RagdollJointLimitWidget.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  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/std/smart_ptr/unique_ptr.h>
  9. #include <AzCore/Serialization/SerializeContext.h>
  10. #include <AzCore/Serialization/EditContext.h>
  11. #include <AzFramework/Physics/Ragdoll.h>
  12. #include <AzFramework/Physics/SystemBus.h>
  13. #include <AzQtComponents/Components/Widgets/CardHeader.h>
  14. #include <MCore/Source/ReflectionSerializer.h>
  15. #include <EMotionFX/Source/Actor.h>
  16. #include <EMotionFX/Source/Node.h>
  17. #include <EMotionFX/Source/TransformData.h>
  18. #include <EMotionFX/CommandSystem/Source/CommandManager.h>
  19. #include <EMotionFX/CommandSystem/Source/RagdollCommands.h>
  20. #include <EMotionFX/CommandSystem/Source/JointLimitCommands.h>
  21. #include <Editor/Plugins/Ragdoll/PhysicsSetupManipulatorBus.h>
  22. #include <Editor/Plugins/Ragdoll/RagdollJointLimitWidget.h>
  23. #include <Editor/SkeletonModel.h>
  24. #include <Editor/ObjectEditor.h>
  25. #include <QComboBox>
  26. #include <QCheckBox>
  27. #include <QGridLayout>
  28. #include <QLabel>
  29. #include <QVBoxLayout>
  30. #include <QSignalBlocker>
  31. #include <QMenu>
  32. namespace EMotionFX
  33. {
  34. int RagdollJointLimitWidget::s_leftMargin = 13;
  35. int RagdollJointLimitWidget::s_textColumnWidth = 142;
  36. void RagdollJointLimitPropertyNotify::AfterPropertyModified([[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
  37. {
  38. PhysicsSetupManipulatorRequestBus::Broadcast(&PhysicsSetupManipulatorRequests::OnUnderlyingPropertiesChanged);
  39. }
  40. RagdollJointLimitWidget::RagdollJointLimitWidget(const AZStd::string& copiedJointLimits, QWidget* parent)
  41. : AzQtComponents::Card(parent)
  42. , m_cardHeaderIcon(SkeletonModel::s_ragdollJointLimitIconPath)
  43. , m_copiedJointLimits(copiedJointLimits)
  44. , m_propertyNotify(AZStd::make_unique<RagdollJointLimitPropertyNotify>())
  45. {
  46. AZ::SerializeContext* serializeContext = nullptr;
  47. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  48. AZ_Error("EMotionFX", serializeContext, "Can't get serialize context from component application.");
  49. setTitle("Joint limit");
  50. AzQtComponents::CardHeader* cardHeader = header();
  51. cardHeader->setIcon(m_cardHeaderIcon);
  52. connect(this, &RagdollJointLimitWidget::contextMenuRequested, this, &RagdollJointLimitWidget::OnCardContextMenu);
  53. QVBoxLayout* vLayout = new QVBoxLayout();
  54. vLayout->setAlignment(Qt::AlignTop);
  55. vLayout->setMargin(0);
  56. QWidget* innerWidget = new QWidget(this);
  57. innerWidget->setLayout(vLayout);
  58. QGridLayout* topLayout = new QGridLayout();
  59. topLayout->setMargin(2);
  60. topLayout->setAlignment(Qt::AlignLeft);
  61. // Has joint limit
  62. QWidget* spacerWidget = new QWidget(this);
  63. spacerWidget->setFixedWidth(s_leftMargin);
  64. topLayout->addWidget(spacerWidget, 0, 0, Qt::AlignLeft);
  65. QLabel* hasLimitLabel = new QLabel("Has joint limit");
  66. hasLimitLabel->setFixedWidth(s_textColumnWidth);
  67. topLayout->addWidget(hasLimitLabel, 0, 1, Qt::AlignLeft);
  68. m_hasLimitCheckbox = new QCheckBox("", this);
  69. connect(m_hasLimitCheckbox, &QCheckBox::stateChanged, this, &RagdollJointLimitWidget::OnHasLimitStateChanged);
  70. topLayout->addWidget(m_hasLimitCheckbox, 0, 2);
  71. // Joint limit type
  72. m_typeLabel = new QLabel("Limit type");
  73. topLayout->addWidget(m_typeLabel, 1, 1, Qt::AlignLeft);
  74. m_typeComboBox = new QComboBox(innerWidget);
  75. m_typeComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
  76. topLayout->addWidget(m_typeComboBox, 1, 2);
  77. vLayout->addLayout(topLayout);
  78. if (serializeContext)
  79. {
  80. //D6 joint is the only currently supported joint for ragdoll
  81. if (auto* jointHelpers = AZ::Interface<AzPhysics::JointHelpersInterface>::Get())
  82. {
  83. if (AZStd::optional<const AZ::TypeId> d6jointTypeId = jointHelpers->GetSupportedJointTypeId(AzPhysics::JointType::D6Joint);
  84. d6jointTypeId.has_value())
  85. {
  86. const char* jointLimitName = serializeContext->FindClassData(*d6jointTypeId)->m_editData->m_name;
  87. m_typeComboBox->addItem(jointLimitName, (*d6jointTypeId).ToString<AZStd::string>().c_str());
  88. }
  89. }
  90. // Reflected property editor for joint limit
  91. m_objectEditor = new EMotionFX::ObjectEditor(serializeContext, m_propertyNotify.get(), innerWidget);
  92. vLayout->addWidget(m_objectEditor);
  93. }
  94. connect(m_typeComboBox, qOverload<int>(&QComboBox::activated), this, &RagdollJointLimitWidget::OnJointTypeChanged);
  95. connect(m_typeComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &RagdollJointLimitWidget::OnJointTypeChanged);
  96. m_commandCallbacks.emplace_back(AZStd::make_unique<DataChangedCallback>(this, false));
  97. CommandSystem::GetCommandManager()->RegisterCommandCallback(CommandAdjustRagdollJoint::s_commandName, m_commandCallbacks.back().get());
  98. CommandSystem::GetCommandManager()->RegisterCommandCallback("AdjustJointLimit", m_commandCallbacks.back().get());
  99. setContentWidget(innerWidget);
  100. setExpanded(true);
  101. }
  102. RagdollJointLimitWidget::~RagdollJointLimitWidget()
  103. {
  104. for (const auto& callback : m_commandCallbacks)
  105. {
  106. CommandSystem::GetCommandManager()->RemoveCommandCallback(callback.get(), false);
  107. }
  108. }
  109. bool RagdollJointLimitWidget::HasJointLimit() const
  110. {
  111. return m_hasLimitCheckbox->isChecked();
  112. }
  113. void RagdollJointLimitWidget::Update(const QModelIndex& modelIndex)
  114. {
  115. m_nodeIndex = modelIndex;
  116. m_objectEditor->ClearInstances(false);
  117. Physics::RagdollNodeConfiguration* ragdollNodeConfig = GetRagdollNodeConfig();
  118. if (ragdollNodeConfig)
  119. {
  120. AzPhysics::JointConfiguration* jointLimitConfig = ragdollNodeConfig->m_jointConfig.get();
  121. if (jointLimitConfig)
  122. {
  123. const AZ::TypeId& jointTypeId = jointLimitConfig->RTTI_GetType();
  124. m_objectEditor->AddInstance(jointLimitConfig, jointTypeId);
  125. // Only show the type combo box in case there is more than one limit type to choose from.
  126. if (m_typeComboBox->count() > 1)
  127. {
  128. m_typeLabel->show();
  129. m_typeComboBox->show();
  130. }
  131. else
  132. {
  133. m_typeLabel->hide();
  134. m_typeComboBox->hide();
  135. }
  136. m_objectEditor->show();
  137. }
  138. else
  139. {
  140. // No joint limit
  141. m_typeLabel->hide();
  142. m_typeComboBox->hide();
  143. m_objectEditor->hide();
  144. }
  145. QSignalBlocker checkboxBlocker(m_hasLimitCheckbox);
  146. m_hasLimitCheckbox->setChecked(jointLimitConfig != nullptr);
  147. }
  148. else
  149. {
  150. m_typeLabel->hide();
  151. m_typeComboBox->hide();
  152. m_objectEditor->hide();
  153. }
  154. }
  155. void RagdollJointLimitWidget::InvalidateValues()
  156. {
  157. m_objectEditor->InvalidateValues();
  158. }
  159. Physics::RagdollNodeConfiguration* RagdollJointLimitWidget::GetRagdollNodeConfig() const
  160. {
  161. Actor* actor = m_nodeIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  162. Node* node = m_nodeIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  163. if (!actor || !node)
  164. {
  165. return nullptr;
  166. }
  167. const AZStd::shared_ptr<EMotionFX::PhysicsSetup> physicsSetup = actor->GetPhysicsSetup();
  168. const Physics::RagdollConfiguration& ragdollConfig = physicsSetup->GetRagdollConfig();
  169. return ragdollConfig.FindNodeConfigByName(node->GetNameString());
  170. }
  171. void RagdollJointLimitWidget::OnHasLimitStateChanged(int state)
  172. {
  173. if (state == Qt::Checked)
  174. {
  175. ChangeLimitType(m_typeComboBox->currentIndex());
  176. }
  177. else
  178. {
  179. ChangeLimitType(AZ::TypeId::CreateNull());
  180. }
  181. }
  182. void RagdollJointLimitWidget::OnCardContextMenu(const QPoint& position)
  183. {
  184. QMenu* contextMenu = new QMenu(this);
  185. contextMenu->setObjectName("EMFX.RagdollJointLimitWidget.ContextMenu");
  186. QAction* copyAction = contextMenu->addAction("Copy joint limits");
  187. copyAction->setObjectName("EMFX.RagdollJointLimitWidget.CopyJointLimitsAction");
  188. connect(copyAction, &QAction::triggered, [this]()
  189. {
  190. Physics::RagdollNodeConfiguration* ragdollNodeConfig = GetRagdollNodeConfig();
  191. if (!ragdollNodeConfig)
  192. {
  193. return;
  194. }
  195. AZ::Outcome<AZStd::string> serialized = CommandAdjustRagdollJoint::SerializeJointLimits(ragdollNodeConfig);
  196. if (!serialized.IsSuccess())
  197. {
  198. return;
  199. }
  200. emit JointLimitCopied(serialized.GetValue());
  201. });
  202. QAction* pasteAction = contextMenu->addAction("Paste joint limits");
  203. pasteAction->setObjectName("EMFX.RagdollJointLimitWidget.PasteJointLimitsAction");
  204. connect(pasteAction, &QAction::triggered, [this]()
  205. {
  206. if (m_copiedJointLimits.empty())
  207. {
  208. return;
  209. }
  210. Physics::RagdollNodeConfiguration* ragdollNodeConfig = GetRagdollNodeConfig();
  211. if (!ragdollNodeConfig)
  212. {
  213. return;
  214. }
  215. Actor* actor = m_nodeIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  216. Node* node = m_nodeIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  217. auto adjustCommand = aznew CommandAdjustRagdollJoint(actor->GetID(), node->GetName(), m_copiedJointLimits);
  218. AZStd::string result;
  219. if (!CommandSystem::GetCommandManager()->ExecuteCommand(adjustCommand, result))
  220. {
  221. AZ_Error("EMotionFX", false, result.c_str());
  222. }
  223. });
  224. pasteAction->setEnabled(!m_copiedJointLimits.empty());
  225. connect(contextMenu, &QMenu::triggered, &QObject::deleteLater);
  226. contextMenu->popup(position);
  227. }
  228. void RagdollJointLimitWidget::ChangeLimitType(const AZ::TypeId& type)
  229. {
  230. Physics::RagdollNodeConfiguration* ragdollNodeConfig = GetRagdollNodeConfig();
  231. if (ragdollNodeConfig)
  232. {
  233. if (type.IsNull())
  234. {
  235. ragdollNodeConfig->m_jointConfig = nullptr;
  236. }
  237. else
  238. {
  239. const Node* node = m_nodeIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  240. const Skeleton* skeleton = m_nodeIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>()->GetSkeleton();
  241. ragdollNodeConfig->m_jointConfig =
  242. CommandRagdollHelpers::CreateJointLimitByType(AzPhysics::JointType::D6Joint, skeleton, node);
  243. }
  244. Update();
  245. }
  246. emit JointLimitTypeChanged();
  247. }
  248. void RagdollJointLimitWidget::ChangeLimitType(int supportedTypeIndex)
  249. {
  250. const QByteArray typeString = m_typeComboBox->itemData(supportedTypeIndex).toString().toUtf8();
  251. const AZ::TypeId newLimitType = AZ::TypeId::CreateString(typeString.data(), typeString.count());
  252. ChangeLimitType(newLimitType);
  253. }
  254. void RagdollJointLimitWidget::OnJointTypeChanged(int index)
  255. {
  256. ChangeLimitType(index);
  257. }
  258. bool RagdollJointLimitWidget::DataChangedCallback::Execute(MCore::Command* command, const MCore::CommandLine& commandLine)
  259. {
  260. AZ_UNUSED(commandLine);
  261. if ((azrtti_typeid(command) == azrtti_typeid<CommandAdjustRagdollJoint>() ||
  262. azrtti_typeid(command) == azrtti_typeid<CommandAdjustJointLimit>()) &&
  263. m_widget->m_nodeIndex.isValid())
  264. {
  265. Node* node = m_widget->m_nodeIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  266. const auto typedCommand = static_cast<CommandAdjustRagdollJoint*>(command);
  267. if (typedCommand->GetJointName() == node->GetName())
  268. {
  269. m_widget->m_objectEditor->InvalidateValues();
  270. }
  271. }
  272. return true;
  273. }
  274. bool RagdollJointLimitWidget::DataChangedCallback::Undo(MCore::Command* command, const MCore::CommandLine& commandLine)
  275. {
  276. return Execute(command, commandLine);
  277. }
  278. } // namespace EMotionFX