ColliderHelpers.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  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/Physics/Character.h>
  9. #include <AzFramework/Physics/ShapeConfiguration.h>
  10. #include <EMotionFX/CommandSystem/Source/ColliderCommands.h>
  11. #include <EMotionFX/CommandSystem/Source/CommandManager.h>
  12. #include <EMotionFX/CommandSystem/Source/RagdollCommands.h>
  13. #include <Editor/ColliderHelpers.h>
  14. #include <Editor/SkeletonModel.h>
  15. #include <MCore/Source/ReflectionSerializer.h>
  16. #include <QAction>
  17. #include <QClipboard>
  18. #include <QGridLayout>
  19. #include <QGuiApplication>
  20. #include <QMenu>
  21. #include <QMimeData>
  22. #include <QObject>
  23. #include <QPushButton>
  24. namespace EMotionFX
  25. {
  26. void ColliderHelpers::AddCopyColliderCommandToGroup(
  27. const Actor* actor,
  28. const Node* joint,
  29. PhysicsSetup::ColliderConfigType copyFrom,
  30. PhysicsSetup::ColliderConfigType copyTo,
  31. MCore::CommandGroup& commandGroup)
  32. {
  33. const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
  34. const Physics::CharacterColliderConfiguration* copyFromColliderConfig = physicsSetup->GetColliderConfigByType(copyFrom);
  35. if (!copyFromColliderConfig)
  36. {
  37. return;
  38. }
  39. const Physics::CharacterColliderNodeConfiguration* copyFromNodeConfig =
  40. copyFromColliderConfig->FindNodeConfigByName(joint->GetNameString());
  41. if (copyFromNodeConfig)
  42. {
  43. for (const AzPhysics::ShapeColliderPair& shapeConfigPair : copyFromNodeConfig->m_shapes)
  44. {
  45. const AZStd::string contents = MCore::ReflectionSerializer::Serialize(&shapeConfigPair).GetValue();
  46. CommandColliderHelpers::AddCollider(
  47. actor->GetID(), joint->GetNameString(), copyTo, contents, AZStd::nullopt, &commandGroup);
  48. }
  49. }
  50. }
  51. void ColliderHelpers::CopyColliders(
  52. const QModelIndexList& modelIndices,
  53. PhysicsSetup::ColliderConfigType copyFrom,
  54. PhysicsSetup::ColliderConfigType copyTo,
  55. bool removeExistingColliders)
  56. {
  57. if (modelIndices.empty())
  58. {
  59. return;
  60. }
  61. const AZStd::string groupName = AZStd::string::format(
  62. "Copy %s collider to %s",
  63. PhysicsSetup::GetStringForColliderConfigType(copyFrom),
  64. PhysicsSetup::GetStringForColliderConfigType(copyTo));
  65. MCore::CommandGroup commandGroup(groupName);
  66. AZStd::string contents;
  67. for (const QModelIndex& selectedIndex : modelIndices)
  68. {
  69. if (SkeletonModel::IndexIsRootNode(selectedIndex))
  70. {
  71. continue;
  72. }
  73. const Actor* actor = selectedIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  74. const Node* joint = selectedIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  75. if (removeExistingColliders)
  76. {
  77. CommandColliderHelpers::ClearColliders(actor->GetID(), joint->GetNameString(), copyTo, &commandGroup);
  78. }
  79. AddCopyColliderCommandToGroup(actor, joint, copyFrom, copyTo, commandGroup);
  80. }
  81. AZStd::string result;
  82. if (!CommandSystem::GetCommandManager()->ExecuteCommandGroup(commandGroup, result))
  83. {
  84. AZ_Error("EMotionFX", false, result.c_str());
  85. }
  86. }
  87. void ColliderHelpers::AddCollider(
  88. const QModelIndexList& modelIndices, PhysicsSetup::ColliderConfigType addTo, const AZ::TypeId& colliderType)
  89. {
  90. if (modelIndices.empty())
  91. {
  92. return;
  93. }
  94. const AZStd::string groupName = AZStd::string::format("Add %s colliders", PhysicsSetup::GetStringForColliderConfigType(addTo));
  95. MCore::CommandGroup commandGroup(groupName);
  96. for (const QModelIndex& selectedIndex : modelIndices)
  97. {
  98. if (SkeletonModel::IndexIsRootNode(selectedIndex))
  99. {
  100. continue;
  101. }
  102. const Actor* actor = selectedIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  103. const Node* joint = selectedIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  104. CommandColliderHelpers::AddCollider(actor->GetID(), joint->GetNameString(), addTo, colliderType, &commandGroup);
  105. }
  106. AZStd::string result;
  107. if (!CommandSystem::GetCommandManager()->ExecuteCommandGroup(commandGroup, result))
  108. {
  109. AZ_Error("EMotionFX", false, result.c_str());
  110. }
  111. }
  112. void ColliderHelpers::ClearColliders(const QModelIndexList& modelIndices, PhysicsSetup::ColliderConfigType removeFrom)
  113. {
  114. if (modelIndices.empty())
  115. {
  116. return;
  117. }
  118. const AZStd::string groupName =
  119. AZStd::string::format("Remove %s colliders", PhysicsSetup::GetStringForColliderConfigType(removeFrom));
  120. MCore::CommandGroup commandGroup(groupName);
  121. for (const QModelIndex& selectedIndex : modelIndices)
  122. {
  123. if (SkeletonModel::IndexIsRootNode(selectedIndex))
  124. {
  125. continue;
  126. }
  127. const Actor* actor = selectedIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  128. const Node* joint = selectedIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  129. CommandColliderHelpers::ClearColliders(actor->GetID(), joint->GetNameString(), removeFrom, &commandGroup);
  130. }
  131. AZStd::string result;
  132. if (!CommandSystem::GetCommandManager()->ExecuteCommandGroup(commandGroup, result))
  133. {
  134. AZ_Error("EMotionFX", false, result.c_str());
  135. }
  136. }
  137. bool ColliderHelpers::AreCollidersReflected()
  138. {
  139. AZ::SerializeContext* serializeContext = nullptr;
  140. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  141. if (serializeContext)
  142. {
  143. if (serializeContext->FindClassData(azrtti_typeid<Physics::SphereShapeConfiguration>()) &&
  144. serializeContext->FindClassData(azrtti_typeid<Physics::BoxShapeConfiguration>()) &&
  145. serializeContext->FindClassData(azrtti_typeid<Physics::CapsuleShapeConfiguration>()))
  146. {
  147. return true;
  148. }
  149. }
  150. return false;
  151. }
  152. bool ColliderHelpers::CanCopyFrom(const QModelIndexList& modelIndices, PhysicsSetup::ColliderConfigType copyFrom)
  153. {
  154. if (modelIndices.isEmpty())
  155. {
  156. return false;
  157. }
  158. for (const QModelIndex& selectedIndex : modelIndices)
  159. {
  160. if (SkeletonModel::IndexIsRootNode(selectedIndex))
  161. {
  162. continue;
  163. }
  164. const Actor* actor = selectedIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  165. const Node* joint = selectedIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  166. const Physics::CharacterColliderConfiguration* config = actor->GetPhysicsSetup()->GetColliderConfigByType(copyFrom);
  167. if (config->FindNodeConfigByName(joint->GetNameString()))
  168. {
  169. return true;
  170. }
  171. }
  172. return false;
  173. }
  174. void ColliderHelpers::AddToRagdoll(const QModelIndexList& modelIndices)
  175. {
  176. if (modelIndices.empty())
  177. {
  178. return;
  179. }
  180. const AZStd::string groupName = AZStd::string::format("Add joint%s to ragdoll", modelIndices.size() > 1 ? "s" : "");
  181. MCore::CommandGroup commandGroup(groupName);
  182. AZStd::vector<AZStd::string> jointNames;
  183. jointNames.reserve(modelIndices.size());
  184. // All the actor pointers should be the same
  185. const uint32 actorId = modelIndices[0].data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>()->GetID();
  186. for (const QModelIndex& selectedIndex : modelIndices)
  187. {
  188. if (SkeletonModel::IndexIsRootNode(selectedIndex))
  189. {
  190. continue;
  191. }
  192. const Node* joint = selectedIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  193. jointNames.emplace_back(joint->GetNameString());
  194. }
  195. CommandRagdollHelpers::AddJointsToRagdoll(actorId, jointNames, &commandGroup);
  196. AZStd::string result;
  197. if (!CommandSystem::GetCommandManager()->ExecuteCommandGroup(commandGroup, result))
  198. {
  199. AZ_Error("EMotionFX", false, result.c_str());
  200. }
  201. }
  202. void ColliderHelpers::RemoveFromRagdoll(const QModelIndexList& modelIndices)
  203. {
  204. if (modelIndices.empty())
  205. {
  206. return;
  207. }
  208. const AZStd::string groupName = AZStd::string::format("Remove joint%s from ragdoll", modelIndices.size() > 1 ? "s" : "");
  209. MCore::CommandGroup commandGroup(groupName);
  210. AZStd::vector<AZStd::string> jointNamesToRemove;
  211. for (const QModelIndex& selectedIndex : modelIndices)
  212. {
  213. if (SkeletonModel::IndexIsRootNode(selectedIndex))
  214. {
  215. continue;
  216. }
  217. const Node* selectedJoint = selectedIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  218. jointNamesToRemove.emplace_back(selectedJoint->GetNameString());
  219. }
  220. const Actor* actor = modelIndices[0].data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  221. CommandRagdollHelpers::RemoveJointsFromRagdoll(actor->GetID(), jointNamesToRemove, &commandGroup);
  222. AZStd::string result;
  223. if (!CommandSystem::GetCommandManager()->ExecuteCommandGroup(commandGroup, result))
  224. {
  225. AZ_Error("EMotionFX", false, result.c_str());
  226. }
  227. }
  228. void ColliderHelpers::AddCopyFromMenu(
  229. QObject* parent,
  230. QMenu* parentMenu,
  231. PhysicsSetup::ColliderConfigType createForType,
  232. const QModelIndexList& modelIndices,
  233. const AZStd::function<void(PhysicsSetup::ColliderConfigType copyFrom, PhysicsSetup::ColliderConfigType copyTo)>& copyFunc)
  234. {
  235. QMenu* copyFromMenu = parentMenu->addMenu("Copy from existing colliders");
  236. for (int i = 0; i < PhysicsSetup::ColliderConfigType::Unknown; ++i)
  237. {
  238. const PhysicsSetup::ColliderConfigType copyFrom = static_cast<PhysicsSetup::ColliderConfigType>(i);
  239. if (copyFrom == createForType)
  240. {
  241. continue;
  242. }
  243. QAction* action = copyFromMenu->addAction(PhysicsSetup::GetVisualNameForColliderConfigType(copyFrom));
  244. const bool canCopyFrom = CanCopyFrom(modelIndices, copyFrom);
  245. if (canCopyFrom)
  246. {
  247. QObject::connect(
  248. action,
  249. &QAction::triggered,
  250. parent,
  251. [copyFunc, copyFrom, createForType]
  252. {
  253. copyFunc(copyFrom, createForType);
  254. });
  255. }
  256. else
  257. {
  258. action->setEnabled(false);
  259. }
  260. }
  261. }
  262. void ColliderHelpers::AddCopyFromMenu(
  263. QObject* parent, QMenu* parentMenu, PhysicsSetup::ColliderConfigType createForType, const QModelIndexList& modelIndices)
  264. {
  265. AddCopyFromMenu(
  266. parent,
  267. parentMenu,
  268. createForType,
  269. modelIndices,
  270. [modelIndices](PhysicsSetup::ColliderConfigType copyFrom, PhysicsSetup::ColliderConfigType copyTo)
  271. {
  272. ColliderHelpers::CopyColliders(modelIndices, copyFrom, copyTo);
  273. });
  274. }
  275. void ColliderHelpers::CopyColliderToClipboard(const QModelIndex& modelIndex, size_t shapeIndex, PhysicsSetup::ColliderConfigType type)
  276. {
  277. if (!modelIndex.isValid())
  278. {
  279. return;
  280. }
  281. const Actor* actor = modelIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  282. const Node* joint = modelIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  283. const Physics::CharacterColliderConfiguration* copyFromColliderConfig = actor->GetPhysicsSetup()->GetColliderConfigByType(type);
  284. if (!copyFromColliderConfig)
  285. {
  286. return;
  287. }
  288. const Physics::CharacterColliderNodeConfiguration* copyFromNodeConfig =
  289. copyFromColliderConfig->FindNodeConfigByName(joint->GetNameString());
  290. if (copyFromNodeConfig && shapeIndex < copyFromNodeConfig->m_shapes.size())
  291. {
  292. const AzPhysics::ShapeColliderPair* shape = &copyFromNodeConfig->m_shapes[shapeIndex];
  293. const AZStd::string contents = MCore::ReflectionSerializer::Serialize(shape).GetValue();
  294. QMimeData* mimeData = new QMimeData();
  295. mimeData->setData(GetMimeTypeForColliderShape(), QByteArray(contents.data(), static_cast<int>(contents.size())));
  296. QGuiApplication::clipboard()->setMimeData(mimeData);
  297. }
  298. }
  299. void ColliderHelpers::PasteColliderFromClipboard(
  300. const QModelIndex& modelIndex, size_t shapeIndex, PhysicsSetup::ColliderConfigType type, bool replace)
  301. {
  302. const QClipboard* clipboard = QGuiApplication::clipboard();
  303. const QMimeData* mimeData = clipboard->mimeData();
  304. const QByteArray clipboardContents = mimeData->data(GetMimeTypeForColliderShape());
  305. if (clipboardContents.isEmpty())
  306. {
  307. return;
  308. }
  309. const Actor* actor = modelIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  310. const Node* joint = modelIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  311. const Physics::CharacterColliderConfiguration* pasteToColliderConfig = actor->GetPhysicsSetup()->GetColliderConfigByType(type);
  312. if (!pasteToColliderConfig)
  313. {
  314. return;
  315. }
  316. const Physics::CharacterColliderNodeConfiguration* pasteToNodeConfig =
  317. pasteToColliderConfig->FindNodeConfigByName(joint->GetNameString());
  318. if (!pasteToNodeConfig && !replace && shapeIndex > 0)
  319. {
  320. return;
  321. }
  322. MCore::CommandGroup commandGroup("Paste collider");
  323. if (replace && shapeIndex < pasteToNodeConfig->m_shapes.size())
  324. {
  325. // Replace the existing one
  326. CommandColliderHelpers::RemoveCollider(actor->GetID(), joint->GetNameString(), type, shapeIndex, &commandGroup);
  327. }
  328. CommandColliderHelpers::AddCollider(
  329. actor->GetID(),
  330. joint->GetNameString(),
  331. type,
  332. AZStd::string(clipboardContents.data(), clipboardContents.size()),
  333. shapeIndex,
  334. &commandGroup);
  335. AZStd::string result;
  336. CommandSystem::GetCommandManager()->ExecuteCommandGroup(commandGroup, result);
  337. }
  338. bool ColliderHelpers::NodeHasRagdoll(const QModelIndex& modelIndex)
  339. {
  340. const Actor* actor = modelIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  341. const Node* joint = modelIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  342. const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
  343. const Physics::RagdollConfiguration& ragdollConfig = physicsSetup->GetRagdollConfig();
  344. return ragdollConfig.FindNodeConfigByName(joint->GetNameString()) != nullptr;
  345. }
  346. bool ColliderHelpers::NodeHasClothCollider(const QModelIndex& modelIndex)
  347. {
  348. const Actor* actor = modelIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  349. const Node* joint = modelIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  350. const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
  351. const auto& clothConfig = physicsSetup->GetClothConfig();
  352. return clothConfig.FindNodeConfigByName(joint->GetNameString()) != nullptr;
  353. }
  354. bool ColliderHelpers::NodeHasHitDetection(const QModelIndex& modelIndex)
  355. {
  356. const Actor* actor = modelIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
  357. const Node* joint = modelIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
  358. const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
  359. const auto& hitDetectionConfig = physicsSetup->GetHitDetectionConfig();
  360. return hitDetectionConfig.FindNodeConfigByName(joint->GetNameString()) != nullptr;
  361. }
  362. } // namespace EMotionFX