DebugDraw.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  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 <Editor/DebugDraw.h>
  9. #include <Editor/ConfigurationWindowBus.h>
  10. #include <AzCore/Component/TransformBus.h>
  11. #include <AzCore/Component/ComponentApplicationBus.h>
  12. #include <AzCore/Component/TickBus.h>
  13. #include <AzCore/Interface/Interface.h>
  14. #include <AzFramework/Entity/EntityDebugDisplayBus.h>
  15. #include <AzToolsFramework/API/ToolsApplicationAPI.h>
  16. #include <LyViewPaneNames.h>
  17. #include <LmbrCentral/Geometry/GeometrySystemComponentBus.h>
  18. #include <Source/Utils.h>
  19. #include <PhysX/Material/PhysXMaterial.h>
  20. #include <PhysX/Debug/PhysXDebugInterface.h>
  21. #include <PhysX/MathConversion.h>
  22. namespace PhysX
  23. {
  24. namespace DebugDraw
  25. {
  26. const AZ::u32 TrianglesWarningThreshold = 16384 * 3;
  27. const AZ::u32 MaxTrianglesRange = 800;
  28. const AZ::Color WarningColor(1.0f, 0.0f, 0.0f, 1.0f);
  29. const float WarningFrequency = 1.0f; // the number of times per second to flash
  30. const AZ::Color WireframeColor(0.0f, 0.0f, 0.0f, 0.7f);
  31. const float ColliderLineWidth = 2.0f;
  32. void OpenPhysXSettingsWindow()
  33. {
  34. // Open configuration window
  35. AzToolsFramework::EditorRequestBus::Broadcast(&AzToolsFramework::EditorRequests::OpenViewPane,
  36. LyViewPane::PhysXConfigurationEditor);
  37. // Set to Global Settings configuration tab
  38. Editor::ConfigurationWindowRequestBus::Broadcast(&Editor::ConfigurationWindowRequests::ShowGlobalSettingsTab);
  39. }
  40. bool IsGlobalColliderDebugCheck(Debug::DebugDisplayData::GlobalCollisionDebugState requiredState)
  41. {
  42. if (auto* physXDebug = AZ::Interface<Debug::PhysXDebugInterface>::Get())
  43. {
  44. return physXDebug->GetDebugDisplayData().m_globalCollisionDebugDraw == requiredState;
  45. }
  46. return false;
  47. }
  48. bool IsDrawColliderReadOnly()
  49. {
  50. bool helpersVisible = false;
  51. AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::BroadcastResult(
  52. helpersVisible, &AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Events::HelpersVisible);
  53. // if helpers are visible, draw colliders is not read only and can be changed
  54. return !helpersVisible;
  55. }
  56. static void BuildAABBVerts(const AZ::Aabb& aabb,
  57. AZStd::vector<AZ::Vector3>& verts,
  58. AZStd::vector<AZ::Vector3>& points,
  59. AZStd::vector<AZ::u32>& indices)
  60. {
  61. struct Triangle
  62. {
  63. AZ::Vector3 m_points[3];
  64. Triangle(const AZ::Vector3& point0, const AZ::Vector3& point1, const AZ::Vector3& point2)
  65. : m_points{point0, point1, point2}
  66. {}
  67. };
  68. const AZ::Vector3 aabbMin = aabb.GetMin();
  69. const AZ::Vector3 aabbMax = aabb.GetMax();
  70. const float x[2] = { aabbMin.GetX(), aabbMax.GetX() };
  71. const float y[2] = { aabbMin.GetY(), aabbMax.GetY() };
  72. const float z[2] = { aabbMin.GetZ(), aabbMax.GetZ() };
  73. // There are 12 triangles in an AABB
  74. const AZStd::vector<Triangle> triangles =
  75. {
  76. // Bottom
  77. {AZ::Vector3(x[0], y[0], z[0]), AZ::Vector3(x[1], y[1], z[0]), AZ::Vector3(x[1], y[0], z[0])},
  78. {AZ::Vector3(x[0], y[0], z[0]), AZ::Vector3(x[0], y[1], z[0]), AZ::Vector3(x[1], y[1], z[0])},
  79. // Top
  80. {AZ::Vector3(x[0], y[0], z[1]), AZ::Vector3(x[1], y[0], z[1]), AZ::Vector3(x[1], y[1], z[1])},
  81. {AZ::Vector3(x[0], y[0], z[1]), AZ::Vector3(x[1], y[1], z[1]), AZ::Vector3(x[0], y[1], z[1])},
  82. // Near
  83. {AZ::Vector3(x[0], y[0], z[0]), AZ::Vector3(x[1], y[0], z[1]), AZ::Vector3(x[1], y[0], z[1])},
  84. {AZ::Vector3(x[0], y[0], z[0]), AZ::Vector3(x[1], y[0], z[1]), AZ::Vector3(x[0], y[0], z[1])},
  85. // Far
  86. {AZ::Vector3(x[0], y[1], z[0]), AZ::Vector3(x[1], y[1], z[1]), AZ::Vector3(x[0], y[1], z[1])},
  87. {AZ::Vector3(x[0], y[1], z[0]), AZ::Vector3(x[1], y[1], z[0]), AZ::Vector3(x[1], y[1], z[1])},
  88. // Left
  89. {AZ::Vector3(x[0], y[1], z[0]), AZ::Vector3(x[0], y[0], z[1]), AZ::Vector3(x[0], y[1], z[1])},
  90. {AZ::Vector3(x[0], y[1], z[0]), AZ::Vector3(x[0], y[0], z[0]), AZ::Vector3(x[0], y[0], z[1])},
  91. // Right
  92. {AZ::Vector3(x[1], y[0], z[0]), AZ::Vector3(x[1], y[1], z[0]), AZ::Vector3(x[1], y[1], z[1])},
  93. {AZ::Vector3(x[1], y[0], z[0]), AZ::Vector3(x[1], y[1], z[1]), AZ::Vector3(x[1], y[0], z[1])}
  94. };
  95. int index = static_cast<int>(verts.size());
  96. verts.reserve(verts.size() + triangles.size() * 3);
  97. indices.reserve(indices.size() + triangles.size() * 3);
  98. points.reserve(points.size() + triangles.size() * 6);
  99. for (const auto& triangle : triangles)
  100. {
  101. verts.emplace_back(triangle.m_points[0]);
  102. verts.emplace_back(triangle.m_points[1]);
  103. verts.emplace_back(triangle.m_points[2]);
  104. indices.emplace_back(index++);
  105. indices.emplace_back(index++);
  106. indices.emplace_back(index++);
  107. points.emplace_back(triangle.m_points[0]);
  108. points.emplace_back(triangle.m_points[1]);
  109. points.emplace_back(triangle.m_points[1]);
  110. points.emplace_back(triangle.m_points[2]);
  111. points.emplace_back(triangle.m_points[2]);
  112. points.emplace_back(triangle.m_points[0]);
  113. }
  114. }
  115. // Collider
  116. void Collider::Reflect(AZ::ReflectContext* context)
  117. {
  118. if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  119. {
  120. serializeContext->Class<Collider>()
  121. ->Version(1)
  122. ->Field("LocallyEnabled", &Collider::m_locallyEnabled)
  123. ;
  124. if (auto editContext = serializeContext->GetEditContext())
  125. {
  126. using GlobalCollisionDebugState = Debug::DebugDisplayData::GlobalCollisionDebugState;
  127. using VisibilityFunc = bool(*)();
  128. editContext->Class<Collider>(
  129. "PhysX Collider Debug Draw", "Global and per-collider debug draw preferences.")
  130. ->DataElement(AZ::Edit::UIHandlers::CheckBox, &Collider::m_locallyEnabled, "Draw collider",
  131. "Display collider geometry in the viewport.")
  132. ->Attribute(AZ::Edit::Attributes::CheckboxTooltip,
  133. "If set, the geometry of this collider is visible in the viewport. 'Draw Helpers' needs to be enabled to use.")
  134. ->Attribute(AZ::Edit::Attributes::Visibility,
  135. VisibilityFunc{ []() { return IsGlobalColliderDebugCheck(GlobalCollisionDebugState::Manual); } })
  136. ->Attribute(AZ::Edit::Attributes::ReadOnly, &IsDrawColliderReadOnly)
  137. ->UIElement(AZ::Edit::UIHandlers::Button, "Draw collider",
  138. "Display collider geometry in the viewport.")
  139. ->Attribute(AZ::Edit::Attributes::ButtonText, "Global override")
  140. ->Attribute(AZ::Edit::Attributes::ButtonTooltip,
  141. "A global setting is overriding this property (to disable the override, "
  142. "set the Global Collision Debug setting to \"Set manually\" in the PhysX Configuration)."
  143. "'Draw Helpers' needs to be enabled to use.")
  144. ->Attribute(AZ::Edit::Attributes::Visibility,
  145. VisibilityFunc{ []() { return !IsGlobalColliderDebugCheck(GlobalCollisionDebugState::Manual); } })
  146. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &OpenPhysXSettingsWindow)
  147. ->Attribute(AZ::Edit::Attributes::ReadOnly, &IsDrawColliderReadOnly)
  148. ;
  149. }
  150. }
  151. }
  152. Collider::Collider()
  153. : m_debugDisplayDataChangedEvent(
  154. [this]([[maybe_unused]] const PhysX::Debug::DebugDisplayData& data)
  155. {
  156. this->RefreshTreeHelper();
  157. })
  158. {
  159. }
  160. void Collider::Connect(AZ::EntityId entityId)
  161. {
  162. m_entityId = entityId;
  163. AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(m_entityId);
  164. AzToolsFramework::EntitySelectionEvents::Bus::Handler::BusConnect(m_entityId);
  165. }
  166. void Collider::SetDisplayCallback(const DisplayCallback* callback)
  167. {
  168. m_displayCallback = callback;
  169. }
  170. void Collider::Disconnect()
  171. {
  172. m_debugDisplayDataChangedEvent.Disconnect();
  173. AzToolsFramework::ViewportInteraction::ViewportSettingsNotificationBus::Handler::BusDisconnect();
  174. AzToolsFramework::EntitySelectionEvents::Bus::Handler::BusDisconnect();
  175. AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect();
  176. m_displayCallback = nullptr;
  177. m_entityId = AZ::EntityId();
  178. ClearCachedGeometry();
  179. }
  180. bool Collider::HasCachedGeometry() const
  181. {
  182. return !m_geometry.empty();
  183. }
  184. void Collider::ClearCachedGeometry()
  185. {
  186. m_geometry.clear();
  187. }
  188. void Collider::SetDisplayFlag(bool enable)
  189. {
  190. m_locallyEnabled = enable;
  191. }
  192. bool Collider::IsDisplayFlagEnabled() const
  193. {
  194. return m_locallyEnabled;
  195. }
  196. void Collider::BuildMeshes(const Physics::ShapeConfiguration& shapeConfig, AZ::u32 geomIndex) const
  197. {
  198. if (m_geometry.size() <= geomIndex)
  199. {
  200. m_geometry.resize(geomIndex + 1);
  201. }
  202. GeometryData& geom = m_geometry[geomIndex];
  203. AZStd::vector<AZ::Vector3>& verts = geom.m_verts;
  204. AZStd::vector<AZ::Vector3>& points = geom.m_points;
  205. AZStd::vector<AZ::u32>& indices = geom.m_indices;
  206. verts.clear();
  207. indices.clear();
  208. points.clear();
  209. switch (shapeConfig.GetShapeType())
  210. {
  211. case Physics::ShapeType::Sphere:
  212. {
  213. const auto& sphereConfig = static_cast<const Physics::SphereShapeConfiguration&>(shapeConfig);
  214. AZ::Vector3 boxMax = AZ::Vector3(sphereConfig.m_scale * sphereConfig.m_radius);
  215. AZ::Aabb aabb = AZ::Aabb::CreateFromMinMax(-boxMax, boxMax);
  216. BuildAABBVerts(aabb, verts, points, indices);
  217. }
  218. break;
  219. case Physics::ShapeType::Box:
  220. {
  221. const auto& boxConfig = static_cast<const Physics::BoxShapeConfiguration&>(shapeConfig);
  222. AZ::Vector3 boxMax = boxConfig.m_scale * 0.5f * boxConfig.m_dimensions;
  223. AZ::Aabb aabb = AZ::Aabb::CreateFromMinMax(-boxMax, boxMax);
  224. BuildAABBVerts(aabb, verts, points, indices);
  225. }
  226. break;
  227. case Physics::ShapeType::Capsule:
  228. {
  229. const auto& capsuleConfig = static_cast<const Physics::CapsuleShapeConfiguration&>(shapeConfig);
  230. LmbrCentral::CapsuleGeometrySystemRequestBus::Broadcast(
  231. &LmbrCentral::CapsuleGeometrySystemRequestBus::Events::GenerateCapsuleMesh,
  232. capsuleConfig.m_radius * capsuleConfig.m_scale.GetX(),
  233. capsuleConfig.m_height * capsuleConfig.m_scale.GetZ(),
  234. 16, 8, verts, indices, points);
  235. }
  236. break;
  237. case Physics::ShapeType::CookedMesh:
  238. {
  239. const auto& cookedMeshConfig = static_cast<const Physics::CookedMeshShapeConfiguration&>(shapeConfig);
  240. const physx::PxBase* constMeshData = static_cast<const physx::PxBase*>(cookedMeshConfig.GetCachedNativeMesh());
  241. // Specifically removing the const from the meshData pointer because the physx APIs expect this pointer to be non-const.
  242. physx::PxBase* meshData = const_cast<physx::PxBase*>(constMeshData);
  243. if (meshData)
  244. {
  245. if (meshData->is<physx::PxTriangleMesh>())
  246. {
  247. BuildTriangleMesh(meshData, geomIndex);
  248. }
  249. else
  250. {
  251. BuildConvexMesh(meshData, geomIndex);
  252. }
  253. }
  254. break;
  255. }
  256. case Physics::ShapeType::PhysicsAsset:
  257. {
  258. AZ_Error("PhysX", false,
  259. "DebugDraw::Collider::BuildMeshes: Cannot pass PhysicsAsset configuration since it is a collection of shapes. "
  260. "Please iterate over m_colliderShapes in the asset and call this function for each of them. "
  261. "Entity %s, ID: %llu", GetEntityName().c_str(), m_entityId);
  262. break;
  263. }
  264. default:
  265. {
  266. AZ_Error("PhysX", false, "DebugDraw::Collider::BuildMeshes: Unsupported ShapeType %d. Entity %s, ID: %llu",
  267. static_cast<AZ::u32>(shapeConfig.GetShapeType()), GetEntityName().c_str(), m_entityId);
  268. break;
  269. }
  270. }
  271. if ((indices.size() / 3) >= TrianglesWarningThreshold)
  272. {
  273. AZ_Warning("PhysX Debug Draw", false, "Mesh has too many triangles! Entity: '%s', ID: %llu",
  274. GetEntityName().c_str(), m_entityId);
  275. }
  276. }
  277. void Collider::BuildTriangleMesh(physx::PxBase* meshData, AZ::u32 geomIndex) const
  278. {
  279. GeometryData& geom = m_geometry[geomIndex];
  280. AZStd::unordered_map<int, AZStd::vector<AZ::u32>>& triangleIndexesByMaterialSlot = geom.m_triangleIndexesByMaterialSlot;
  281. AZStd::vector<AZ::Vector3>& verts = geom.m_verts;
  282. AZStd::vector<AZ::Vector3>& points = geom.m_points;
  283. AZStd::vector<AZ::u32>& indices = geom.m_indices;
  284. physx::PxTriangleMeshGeometry mesh = physx::PxTriangleMeshGeometry(reinterpret_cast<physx::PxTriangleMesh*>(meshData));
  285. const physx::PxTriangleMesh* triangleMesh = mesh.triangleMesh;
  286. const physx::PxVec3* vertices = triangleMesh->getVertices();
  287. const AZ::u32 vertCount = triangleMesh->getNbVertices();
  288. const AZ::u32 triangleCount = triangleMesh->getNbTriangles();
  289. const void* triangles = triangleMesh->getTriangles();
  290. verts.reserve(vertCount);
  291. indices.reserve(triangleCount * 3);
  292. points.reserve(triangleCount * 3 * 2);
  293. triangleIndexesByMaterialSlot.clear();
  294. physx::PxTriangleMeshFlags triangleMeshFlags = triangleMesh->getTriangleMeshFlags();
  295. const bool mesh16BitVertexIndices = triangleMeshFlags.isSet(physx::PxTriangleMeshFlag::Enum::e16_BIT_INDICES);
  296. auto GetVertIndex = [=](AZ::u32 index) -> AZ::u32
  297. {
  298. if (mesh16BitVertexIndices)
  299. {
  300. return reinterpret_cast<const physx::PxU16*>(triangles)[index];
  301. }
  302. else
  303. {
  304. return reinterpret_cast<const physx::PxU32*>(triangles)[index];
  305. }
  306. };
  307. for (AZ::u32 vertIndex = 0; vertIndex < vertCount; ++vertIndex)
  308. {
  309. AZ::Vector3 vert = PxMathConvert(vertices[vertIndex]);
  310. verts.push_back(vert);
  311. }
  312. for (AZ::u32 triangleIndex = 0; triangleIndex < triangleCount * 3; triangleIndex += 3)
  313. {
  314. AZ::u32 index1 = GetVertIndex(triangleIndex);
  315. AZ::u32 index2 = GetVertIndex(triangleIndex + 1);
  316. AZ::u32 index3 = GetVertIndex(triangleIndex + 2);
  317. AZ::Vector3 a = verts[index1];
  318. AZ::Vector3 b = verts[index2];
  319. AZ::Vector3 c = verts[index3];
  320. indices.push_back(index1);
  321. indices.push_back(index2);
  322. indices.push_back(index3);
  323. points.push_back(a);
  324. points.push_back(b);
  325. points.push_back(b);
  326. points.push_back(c);
  327. points.push_back(c);
  328. points.push_back(a);
  329. const physx::PxMaterialTableIndex materialIndex = triangleMesh->getTriangleMaterialIndex(triangleIndex / 3);
  330. const int slotIndex = static_cast<const int>(materialIndex);
  331. triangleIndexesByMaterialSlot[slotIndex].push_back(index1);
  332. triangleIndexesByMaterialSlot[slotIndex].push_back(index2);
  333. triangleIndexesByMaterialSlot[slotIndex].push_back(index3);
  334. }
  335. }
  336. void Collider::BuildConvexMesh(physx::PxBase* meshData, AZ::u32 geomIndex) const
  337. {
  338. GeometryData& geom = m_geometry[geomIndex];
  339. AZStd::vector<AZ::Vector3>& verts = geom.m_verts;
  340. AZStd::vector<AZ::Vector3>& points = geom.m_points;
  341. physx::PxConvexMeshGeometry mesh = physx::PxConvexMeshGeometry(reinterpret_cast<physx::PxConvexMesh*>(meshData));
  342. const physx::PxConvexMesh* convexMesh = mesh.convexMesh;
  343. const physx::PxU8* pxIndices = convexMesh->getIndexBuffer();
  344. const physx::PxVec3* pxVertices = convexMesh->getVertices();
  345. const AZ::u32 numPolys = convexMesh->getNbPolygons();
  346. for (AZ::u32 polygonIndex = 0; polygonIndex < numPolys; ++polygonIndex)
  347. {
  348. physx::PxHullPolygon poly;
  349. convexMesh->getPolygonData(polygonIndex, poly);
  350. AZ::u32 index1 = 0;
  351. AZ::u32 index2 = 1;
  352. AZ::u32 index3 = 2;
  353. const AZ::Vector3 a = PxMathConvert(pxVertices[pxIndices[poly.mIndexBase + index1]]);
  354. const AZ::u32 triangleCount = poly.mNbVerts - 2;
  355. for (AZ::u32 triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex)
  356. {
  357. AZ_Assert(index3 < poly.mNbVerts, "Implementation error: attempted to index outside range of polygon vertices.");
  358. const AZ::Vector3 b = PxMathConvert(pxVertices[pxIndices[poly.mIndexBase + index2]]);
  359. const AZ::Vector3 c = PxMathConvert(pxVertices[pxIndices[poly.mIndexBase + index3]]);
  360. verts.push_back(a);
  361. verts.push_back(b);
  362. verts.push_back(c);
  363. points.push_back(a);
  364. points.push_back(b);
  365. points.push_back(b);
  366. points.push_back(c);
  367. points.push_back(c);
  368. points.push_back(a);
  369. index2 = index3++;
  370. }
  371. }
  372. }
  373. AZ::Color Collider::CalcDebugColor(const Physics::ColliderConfiguration& colliderConfig
  374. , const ElementDebugInfo& elementDebugInfo) const
  375. {
  376. using GlobalCollisionDebugColorMode = Debug::DebugDisplayData::GlobalCollisionDebugColorMode;
  377. AZ::Color debugColor = AZ::Colors::White;
  378. GlobalCollisionDebugColorMode debugDrawColorMode = GlobalCollisionDebugColorMode::MaterialColor;
  379. if (auto* physXDebug = AZ::Interface<Debug::PhysXDebugInterface>::Get())
  380. {
  381. debugDrawColorMode = physXDebug->GetDebugDisplayData().m_globalCollisionDebugDrawColorMode;
  382. }
  383. switch (debugDrawColorMode)
  384. {
  385. case GlobalCollisionDebugColorMode::MaterialColor:
  386. {
  387. const auto materialAsset = colliderConfig.m_materialSlots.GetMaterialAsset(elementDebugInfo.m_materialSlotIndex);
  388. AZStd::shared_ptr<Material> material = Material::FindOrCreateMaterial(materialAsset);
  389. if (material)
  390. {
  391. debugColor = material->GetDebugColor();
  392. }
  393. break;
  394. }
  395. case GlobalCollisionDebugColorMode::ErrorColor:
  396. {
  397. debugColor = CalcDebugColorWarning(debugColor, elementDebugInfo.m_numTriangles);
  398. break;
  399. }
  400. // Don't add default case, compilation warning C4062 will happen if user adds a new ColorMode to the enum and not handles the case
  401. }
  402. debugColor.SetA(0.5f);
  403. return debugColor;
  404. }
  405. AZ::Color Collider::CalcDebugColorWarning(const AZ::Color& currentColor, AZ::u32 triangleCount) const
  406. {
  407. // Show glowing warning color when the triangle count exceeds the maximum allowed triangles
  408. if (triangleCount > TrianglesWarningThreshold)
  409. {
  410. AZ::ScriptTimePoint currentTimePoint;
  411. AZ::TickRequestBus::BroadcastResult(currentTimePoint, &AZ::TickRequests::GetTimeAtCurrentTick);
  412. float currentTime = static_cast<float>(currentTimePoint.GetSeconds());
  413. float alpha = fabsf(sinf(currentTime * AZ::Constants::HalfPi * WarningFrequency));
  414. alpha *= static_cast<float>(AZStd::GetMin(MaxTrianglesRange, triangleCount - TrianglesWarningThreshold)) /
  415. static_cast<float>(TrianglesWarningThreshold);
  416. return currentColor * (1.0f - alpha) + WarningColor * alpha;
  417. }
  418. return currentColor;
  419. }
  420. void Collider::DrawSphere(AzFramework::DebugDisplayRequests& debugDisplay,
  421. const Physics::ColliderConfiguration& colliderConfig,
  422. const Physics::SphereShapeConfiguration& sphereShapeConfig,
  423. const AZ::Vector3& colliderScale) const
  424. {
  425. const float scaledSphereRadius =
  426. (Utils::GetTransformScale(m_entityId) * colliderScale).GetMaxElement() * sphereShapeConfig.m_radius;
  427. debugDisplay.PushMatrix(GetColliderLocalTransform(colliderConfig, colliderScale));
  428. debugDisplay.SetColor(CalcDebugColor(colliderConfig));
  429. debugDisplay.DrawBall(AZ::Vector3::CreateZero(), scaledSphereRadius);
  430. debugDisplay.SetColor(WireframeColor);
  431. debugDisplay.DrawWireSphere(AZ::Vector3::CreateZero(), scaledSphereRadius);
  432. debugDisplay.PopMatrix();
  433. }
  434. void Collider::DrawBox(
  435. AzFramework::DebugDisplayRequests& debugDisplay,
  436. const Physics::ColliderConfiguration& colliderConfig,
  437. const Physics::BoxShapeConfiguration& boxShapeConfig,
  438. const AZ::Vector3& colliderScale) const
  439. {
  440. // The resulting scale is the product of the scale in the entity's transform and the collider scale.
  441. const AZ::Vector3 resultantScale = Utils::GetTransformScale(m_entityId) * colliderScale;
  442. // Scale the box parameters using the desired method (uniform or non-uniform).
  443. const AZ::Vector3 scaledBoxParameters = boxShapeConfig.m_dimensions * 0.5f * resultantScale;
  444. const AZ::Color& faceColor = CalcDebugColor(colliderConfig);
  445. debugDisplay.PushMatrix(GetColliderLocalTransform(colliderConfig, colliderScale));
  446. debugDisplay.SetColor(faceColor);
  447. debugDisplay.DrawSolidBox(-scaledBoxParameters, scaledBoxParameters);
  448. debugDisplay.SetColor(WireframeColor);
  449. debugDisplay.DrawWireBox(-scaledBoxParameters, scaledBoxParameters);
  450. debugDisplay.PopMatrix();
  451. }
  452. void Collider::DrawCapsule(AzFramework::DebugDisplayRequests& debugDisplay,
  453. const Physics::ColliderConfiguration& colliderConfig,
  454. const Physics::CapsuleShapeConfiguration& capsuleShapeConfig,
  455. const AZ::Vector3& colliderScale) const
  456. {
  457. AZStd::vector<AZ::Vector3> verts;
  458. AZStd::vector<AZ::Vector3> points;
  459. AZStd::vector<AZ::u32> indices;
  460. // The resulting scale is the product of the scale in the entity's transform and the collider scale.
  461. const AZ::Vector3 resultantScale = Utils::GetTransformScale(m_entityId) * colliderScale;
  462. // Scale the capsule parameters using the desired method (uniform or non-uniform).
  463. AZ::Vector2 scaledCapsuleParameters = AZ::Vector2(capsuleShapeConfig.m_radius, capsuleShapeConfig.m_height);
  464. scaledCapsuleParameters *= AZ::Vector2(AZ::GetMax(resultantScale.GetX(), resultantScale.GetY()), resultantScale.GetZ());
  465. debugDisplay.PushMatrix(GetColliderLocalTransform(colliderConfig, colliderScale));
  466. LmbrCentral::CapsuleGeometrySystemRequestBus::Broadcast(
  467. &LmbrCentral::CapsuleGeometrySystemRequestBus::Events::GenerateCapsuleMesh,
  468. scaledCapsuleParameters.GetX(),
  469. scaledCapsuleParameters.GetY(),
  470. 16, 8, verts, indices, points);
  471. const AZ::Color& faceColor = CalcDebugColor(colliderConfig);
  472. debugDisplay.DrawTrianglesIndexed(verts, indices, faceColor);
  473. debugDisplay.DrawLines(points, WireframeColor);
  474. debugDisplay.SetLineWidth(ColliderLineWidth);
  475. debugDisplay.PopMatrix();
  476. }
  477. void Collider::DrawMesh(AzFramework::DebugDisplayRequests& debugDisplay,
  478. const Physics::ColliderConfiguration& colliderConfig,
  479. const Physics::CookedMeshShapeConfiguration& meshConfig,
  480. const AZ::Vector3& meshScale,
  481. AZ::u32 geomIndex) const
  482. {
  483. if (geomIndex >= m_geometry.size())
  484. {
  485. AZ_Error("PhysX", false, "DrawMesh: geomIndex %d is out of range for %s. Size: %d",
  486. geomIndex, GetEntityName().c_str(), m_geometry.size());
  487. return;
  488. }
  489. if (meshConfig.GetCachedNativeMesh())
  490. {
  491. debugDisplay.PushMatrix(GetColliderLocalTransform(colliderConfig));
  492. if (meshConfig.GetMeshType() == Physics::CookedMeshShapeConfiguration::MeshType::TriangleMesh)
  493. {
  494. DrawTriangleMesh(debugDisplay, colliderConfig, geomIndex, meshScale);
  495. }
  496. else
  497. {
  498. DrawConvexMesh(debugDisplay, colliderConfig, geomIndex, meshScale);
  499. }
  500. debugDisplay.PopMatrix();
  501. }
  502. }
  503. AZStd::vector<AZ::Vector3> ScalePoints(const AZ::Vector3& scale, const AZStd::vector<AZ::Vector3>& points)
  504. {
  505. AZStd::vector<AZ::Vector3> scaledPoints;
  506. scaledPoints.resize_no_construct(points.size());
  507. AZStd::transform(
  508. points.begin(), points.end(), scaledPoints.begin(),
  509. [scale](const AZ::Vector3& point)
  510. {
  511. return scale * point;
  512. });
  513. return scaledPoints;
  514. }
  515. void Collider::DrawTriangleMesh(
  516. AzFramework::DebugDisplayRequests& debugDisplay, const Physics::ColliderConfiguration& colliderConfig, AZ::u32 geomIndex,
  517. const AZ::Vector3& meshScale) const
  518. {
  519. AZ_Assert(geomIndex < m_geometry.size(), "DrawTriangleMesh: geomIndex is out of range");
  520. const GeometryData& geom = m_geometry[geomIndex];
  521. const AZStd::unordered_map<int, AZStd::vector<AZ::u32>>& triangleIndexesByMaterialSlot
  522. = geom.m_triangleIndexesByMaterialSlot;
  523. AZStd::vector<AZ::Vector3> scaledVerts = ScalePoints(meshScale, geom.m_verts);
  524. AZStd::vector<AZ::Vector3> scaledPoints = ScalePoints(meshScale, geom.m_points);
  525. if (!scaledVerts.empty())
  526. {
  527. for (const auto& element : triangleIndexesByMaterialSlot)
  528. {
  529. const int materialSlot = element.first;
  530. const AZStd::vector<AZ::u32>& triangleIndexes = element.second;
  531. const AZ::u32 triangleCount = static_cast<AZ::u32>(triangleIndexes.size() / 3);
  532. ElementDebugInfo triangleMeshInfo;
  533. triangleMeshInfo.m_numTriangles = triangleCount;
  534. triangleMeshInfo.m_materialSlotIndex = materialSlot;
  535. debugDisplay.DrawTrianglesIndexed(scaledVerts, triangleIndexes
  536. , CalcDebugColor(colliderConfig, triangleMeshInfo));
  537. }
  538. debugDisplay.DrawLines(scaledPoints, WireframeColor);
  539. }
  540. }
  541. void Collider::DrawConvexMesh(
  542. AzFramework::DebugDisplayRequests& debugDisplay, const Physics::ColliderConfiguration& colliderConfig, AZ::u32 geomIndex,
  543. const AZ::Vector3& meshScale) const
  544. {
  545. AZ_Assert(geomIndex < m_geometry.size(), "DrawConvexMesh: geomIndex is out of range");
  546. const GeometryData& geom = m_geometry[geomIndex];
  547. AZStd::vector<AZ::Vector3> scaledVerts = ScalePoints(meshScale, geom.m_verts);
  548. AZStd::vector<AZ::Vector3> scaledPoints = ScalePoints(meshScale, geom.m_points);
  549. if (!scaledVerts.empty())
  550. {
  551. const AZ::u32 triangleCount = static_cast<AZ::u32>(scaledVerts.size() / 3);
  552. ElementDebugInfo convexMeshInfo;
  553. convexMeshInfo.m_numTriangles = triangleCount;
  554. debugDisplay.DrawTriangles(scaledVerts, CalcDebugColor(colliderConfig, convexMeshInfo));
  555. debugDisplay.DrawLines(scaledPoints, WireframeColor);
  556. }
  557. }
  558. void Collider::DrawPolygonPrism(AzFramework::DebugDisplayRequests& debugDisplay,
  559. const Physics::ColliderConfiguration& colliderConfig, const AZStd::vector<AZ::Vector3>& points) const
  560. {
  561. if (!points.empty())
  562. {
  563. debugDisplay.PushMatrix(GetColliderLocalTransform(colliderConfig));
  564. debugDisplay.SetLineWidth(ColliderLineWidth);
  565. debugDisplay.DrawLines(points, WireframeColor);
  566. debugDisplay.PopMatrix();
  567. }
  568. }
  569. void Collider::DrawHeightfield(
  570. AzFramework::DebugDisplayRequests& debugDisplay,
  571. const AZ::Vector3& aabbCenterLocalBody,
  572. float drawDistance,
  573. const AZStd::shared_ptr<const Physics::Shape>& shape) const
  574. {
  575. // Shape::GetGeometry expects the bounding box in local space
  576. const AZ::Vector3 shapeOffset = shape->GetLocalPose().first;
  577. const AZ::Vector3 aabbCenterLocalShape = aabbCenterLocalBody - shapeOffset;
  578. // Create the bounds box of the required size
  579. const AZ::Aabb boundsAabb = AZ::Aabb::CreateCenterRadius(aabbCenterLocalShape, drawDistance);
  580. if (!boundsAabb.IsValid())
  581. {
  582. return;
  583. }
  584. // Extract the heightfield geometry within the bounds
  585. AZStd::vector<AZ::Vector3> vertices;
  586. AZStd::vector<AZ::u32> indices;
  587. shape->GetGeometry(vertices, indices, &boundsAabb);
  588. if (!vertices.empty())
  589. {
  590. /*
  591. Each heightfield quad consists of 6 vertices, or 2 triangles.
  592. If we naively draw each triangle, we'll need 6 lines per quad. However, the diagonal line would be drawn twice,
  593. and the quad borders with adjacent quads would also be drawn twice, so we can reduce this down to 3 lines, so
  594. that we're drawing a per-quad pattern like this:
  595. 2 --- 3
  596. |\
  597. 0 | \ 1
  598. To draw 3 lines, we need 6 vertices. Because our results *already* have 6 vertices per quad, we just need to make
  599. sure each set of 6 is the *right* set of vertices for what we want to draw, and then we can submit the entire set
  600. directly to DrawLines().
  601. We currently get back 6 vertices in the pattern 0-1-2, 1-3-2, for our two triangles. The lines we want to draw
  602. are 0-2, 2-1, and 3-2. We can create this pattern by just copying the third vertex onto the second vertex for
  603. every quad so that 0 1 2 1 3 2 becomes 0 2 2 1 3 2.
  604. */
  605. for (size_t vertex = 0; vertex < vertices.size(); vertex += 6)
  606. {
  607. vertices[vertex + 1] = vertices[vertex + 2];
  608. }
  609. // Returned vertices are in the shape-local space, so need to adjust the debug display matrix
  610. const AZ::Transform shapeOffsetTransform = AZ::Transform::CreateTranslation(shapeOffset);
  611. debugDisplay.PushMatrix(shapeOffsetTransform);
  612. debugDisplay.DrawLines(vertices, AZ::Colors::White);
  613. debugDisplay.PopMatrix();
  614. }
  615. }
  616. AZ::Transform Collider::GetColliderLocalTransform(
  617. const Physics::ColliderConfiguration& colliderConfig,
  618. const AZ::Vector3& colliderScale) const
  619. {
  620. // Apply entity world transform scale to collider offset
  621. const AZ::Vector3 translation =
  622. colliderConfig.m_position * Utils::GetTransformScale(m_entityId) * colliderScale;
  623. return AZ::Transform::CreateFromQuaternionAndTranslation(
  624. colliderConfig.m_rotation, translation);
  625. }
  626. const AZStd::vector<AZ::Vector3>& Collider::GetVerts(AZ::u32 geomIndex) const
  627. {
  628. AZ_Assert(geomIndex < m_geometry.size(), "GetVerts: geomIndex %d is out of range for %s. Size: %d",
  629. geomIndex, GetEntityName().c_str(), m_geometry.size());
  630. return m_geometry[geomIndex].m_verts;
  631. }
  632. const AZStd::vector<AZ::Vector3>& Collider::GetPoints(AZ::u32 geomIndex) const
  633. {
  634. AZ_Assert(geomIndex < m_geometry.size(), "GetPoints: geomIndex %d is out of range for %s. Size: %d",
  635. geomIndex, GetEntityName().c_str(), m_geometry.size());
  636. return m_geometry[geomIndex].m_points;
  637. }
  638. const AZStd::vector<AZ::u32>& Collider::GetIndices(AZ::u32 geomIndex) const
  639. {
  640. AZ_Assert(geomIndex < m_geometry.size(), "GetIndices: geomIndex %d is out of range for %s. Size: %d",
  641. geomIndex, GetEntityName().c_str(), m_geometry.size());
  642. return m_geometry[geomIndex].m_indices;
  643. }
  644. AZ::u32 Collider::GetNumShapes() const
  645. {
  646. return static_cast<AZ::u32>(m_geometry.size());
  647. }
  648. // AzFramework::EntityDebugDisplayEventBus
  649. void Collider::DisplayEntityViewport(const AzFramework::ViewportInfo& viewportInfo,
  650. AzFramework::DebugDisplayRequests& debugDisplay)
  651. {
  652. if (!m_displayCallback)
  653. {
  654. return;
  655. }
  656. // Let each collider decide how to scale itself, so extract the scale here.
  657. AZ::Transform entityWorldTransformWithoutScale = AZ::Transform::CreateIdentity();
  658. AZ::TransformBus::EventResult(entityWorldTransformWithoutScale, m_entityId, &AZ::TransformInterface::GetWorldTM);
  659. entityWorldTransformWithoutScale.ExtractUniformScale();
  660. auto* physXDebug = AZ::Interface<Debug::PhysXDebugInterface>::Get();
  661. if (physXDebug == nullptr)
  662. {
  663. return;
  664. }
  665. const PhysX::Debug::DebugDisplayData& displayData = physXDebug->GetDebugDisplayData();
  666. const PhysX::Debug::ColliderProximityVisualization& proximityVisualization = displayData.m_colliderProximityVisualization;
  667. const bool colliderIsInRange =
  668. proximityVisualization.m_cameraPosition.GetDistanceSq(entityWorldTransformWithoutScale.GetTranslation()) <
  669. proximityVisualization.m_radius * proximityVisualization.m_radius;
  670. const PhysX::Debug::DebugDisplayData::GlobalCollisionDebugState& globalCollisionDebugDraw = displayData.m_globalCollisionDebugDraw;
  671. if (globalCollisionDebugDraw != PhysX::Debug::DebugDisplayData::GlobalCollisionDebugState::AlwaysOff)
  672. {
  673. if (globalCollisionDebugDraw == PhysX::Debug::DebugDisplayData::GlobalCollisionDebugState::AlwaysOn
  674. || m_locallyEnabled
  675. || (proximityVisualization.m_enabled && colliderIsInRange))
  676. {
  677. debugDisplay.PushMatrix(entityWorldTransformWithoutScale);
  678. m_displayCallback->Display(viewportInfo, debugDisplay);
  679. debugDisplay.PopMatrix();
  680. }
  681. }
  682. }
  683. void Collider::OnDrawHelpersChanged([[maybe_unused]] bool enabled)
  684. {
  685. RefreshTreeHelper();
  686. }
  687. void Collider::OnSelected()
  688. {
  689. AzToolsFramework::ViewportInteraction::ViewportSettingsNotificationBus::Handler::BusConnect(
  690. AzFramework::g_defaultSceneEntityDebugDisplayId);
  691. if (auto* physXDebug = AZ::Interface<Debug::PhysXDebugInterface>::Get())
  692. {
  693. physXDebug->RegisterDebugDisplayDataChangedEvent(m_debugDisplayDataChangedEvent);
  694. }
  695. }
  696. void Collider::OnDeselected()
  697. {
  698. AzToolsFramework::ViewportInteraction::ViewportSettingsNotificationBus::Handler::BusDisconnect();
  699. m_debugDisplayDataChangedEvent.Disconnect();
  700. }
  701. void Collider::RefreshTreeHelper()
  702. {
  703. AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
  704. &AzToolsFramework::ToolsApplicationEvents::Bus::Events::InvalidatePropertyDisplay, AzToolsFramework::Refresh_AttributesAndValues);
  705. }
  706. AZStd::string Collider::GetEntityName() const
  707. {
  708. AZStd::string entityName;
  709. AZ::ComponentApplicationBus::BroadcastResult(entityName,
  710. &AZ::ComponentApplicationRequests::GetEntityName, m_entityId);
  711. return entityName;
  712. }
  713. } // namespace DebugDraw
  714. } // namespace PhysX