RecastNavigationPhysXProviderComponentController.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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/Console/IConsole.h>
  9. #include <AzCore/Debug/Profiler.h>
  10. #include <AzFramework/Physics/PhysicsScene.h>
  11. #include <AzFramework/Physics/Shape.h>
  12. #include <AzFramework/Physics/ShapeConfiguration.h>
  13. #include <DebugDraw/DebugDrawBus.h>
  14. #include <LmbrCentral/Shape/ShapeComponentBus.h>
  15. #include <Misc/RecastNavigationPhysXProviderComponentController.h>
  16. AZ_CVAR(
  17. bool, cl_navmesh_showInputData, false, nullptr, AZ::ConsoleFunctorFlags::Null,
  18. "If enabled, draws triangle mesh input data that was used for the navigation mesh calculation");
  19. AZ_CVAR(
  20. float, cl_navmesh_showInputDataSeconds, 30.f, nullptr, AZ::ConsoleFunctorFlags::Null,
  21. "If enabled, keeps the debug triangle mesh input for the specified number of seconds");
  22. AZ_CVAR(
  23. AZ::u32, bg_navmesh_tileThreads, 4, nullptr, AZ::ConsoleFunctorFlags::Null,
  24. "Number of threads to use to process tiles for each RecastNavigationPhysXProvider");
  25. AZ_DECLARE_BUDGET(Navigation);
  26. namespace RecastNavigation
  27. {
  28. void RecastNavigationPhysXProviderComponentController::Reflect(AZ::ReflectContext* context)
  29. {
  30. RecastNavigationPhysXProviderConfig::Reflect(context);
  31. if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
  32. {
  33. serialize->Class<RecastNavigationPhysXProviderComponentController>()
  34. ->Field("Config", &RecastNavigationPhysXProviderComponentController::m_config)
  35. ->Version(1);
  36. }
  37. }
  38. void RecastNavigationPhysXProviderComponentController::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  39. {
  40. // This can be used to depend on this specific component.
  41. provided.push_back(AZ_CRC_CE("RecastNavigationPhysXProviderComponentController"));
  42. // Or be able to satisfy requirements of @RecastNavigationMeshComponent, as one of geometry data providers for the navigation mesh.
  43. provided.push_back(AZ_CRC_CE("RecastNavigationProviderService"));
  44. }
  45. void RecastNavigationPhysXProviderComponentController::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  46. {
  47. incompatible.push_back(AZ_CRC_CE("RecastNavigationPhysXProviderComponentController"));
  48. incompatible.push_back(AZ_CRC_CE("RecastNavigationProviderService"));
  49. }
  50. void RecastNavigationPhysXProviderComponentController::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  51. {
  52. required.push_back(AZ_CRC_CE("AxisAlignedBoxShapeService"));
  53. }
  54. RecastNavigationPhysXProviderComponentController::RecastNavigationPhysXProviderComponentController()
  55. : m_taskExecutor(bg_navmesh_tileThreads)
  56. {
  57. }
  58. RecastNavigationPhysXProviderComponentController::RecastNavigationPhysXProviderComponentController(
  59. const RecastNavigationPhysXProviderConfig& config)
  60. : m_config(config)
  61. , m_taskExecutor(bg_navmesh_tileThreads)
  62. {
  63. }
  64. void RecastNavigationPhysXProviderComponentController::Activate(const AZ::EntityComponentIdPair& entityComponentIdPair)
  65. {
  66. m_entityComponentIdPair = entityComponentIdPair;
  67. m_shouldProcessTiles = true;
  68. m_updateInProgress = false;
  69. OnConfigurationChanged();
  70. RecastNavigationProviderRequestBus::Handler::BusConnect(m_entityComponentIdPair.GetEntityId());
  71. }
  72. void RecastNavigationPhysXProviderComponentController::SetConfiguration(const RecastNavigationPhysXProviderConfig& config)
  73. {
  74. m_config = config;
  75. }
  76. const RecastNavigationPhysXProviderConfig& RecastNavigationPhysXProviderComponentController::GetConfiguration() const
  77. {
  78. return m_config;
  79. }
  80. void RecastNavigationPhysXProviderComponentController::Deactivate()
  81. {
  82. if (m_updateInProgress)
  83. {
  84. m_shouldProcessTiles = false;
  85. if (m_taskGraphEvent && m_taskGraphEvent->IsSignaled() == false)
  86. {
  87. // If the tasks are still in progress, wait until the task graph is finished.
  88. m_taskGraphEvent->Wait();
  89. }
  90. }
  91. m_updateInProgress = false;
  92. RecastNavigationProviderRequestBus::Handler::BusDisconnect();
  93. // The event is used to detect if tasks are already in progress.
  94. m_taskGraphEvent.reset();
  95. }
  96. AZStd::vector<AZStd::shared_ptr<TileGeometry>> RecastNavigationPhysXProviderComponentController::CollectGeometry(
  97. float tileSize, float borderSize)
  98. {
  99. // Blocking call.
  100. return CollectGeometryImpl(tileSize, borderSize, GetWorldBounds());
  101. }
  102. bool RecastNavigationPhysXProviderComponentController::CollectGeometryAsync(
  103. float tileSize,
  104. float borderSize,
  105. AZStd::function<void(AZStd::shared_ptr<TileGeometry>)> tileCallback)
  106. {
  107. return CollectGeometryAsyncImpl(tileSize, borderSize, GetWorldBounds(), AZStd::move(tileCallback));
  108. }
  109. AZ::Aabb RecastNavigationPhysXProviderComponentController::GetWorldBounds() const
  110. {
  111. AZ::Aabb worldBounds = AZ::Aabb::CreateNull();
  112. LmbrCentral::ShapeComponentRequestsBus::EventResult(worldBounds, m_entityComponentIdPair.GetEntityId(),
  113. &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
  114. return worldBounds;
  115. }
  116. int RecastNavigationPhysXProviderComponentController::GetNumberOfTiles(float tileSize) const
  117. {
  118. const AZ::Aabb worldVolume = GetWorldBounds();
  119. const AZ::Vector3 extents = worldVolume.GetExtents();
  120. const int tilesAlongX = aznumeric_cast<int>(AZStd::ceil(extents.GetX() / tileSize));
  121. const int tilesAlongY = aznumeric_cast<int>(AZStd::ceil(extents.GetY() / tileSize));
  122. return tilesAlongX * tilesAlongY;
  123. }
  124. const char* RecastNavigationPhysXProviderComponentController::GetSceneName() const
  125. {
  126. return m_config.m_useEditorScene ? AzPhysics::EditorPhysicsSceneName : AzPhysics::DefaultPhysicsSceneName;
  127. }
  128. void RecastNavigationPhysXProviderComponentController::OnConfigurationChanged()
  129. {
  130. m_collisionGroup = GetCollisionGroupById(m_config.m_collisionGroupId);
  131. }
  132. void RecastNavigationPhysXProviderComponentController::CollectCollidersWithinVolume(const AZ::Aabb& volume, QueryHits& overlapHits)
  133. {
  134. AZ_PROFILE_SCOPE(Navigation, "Navigation: CollectGeometryWithinVolume");
  135. AZ::Vector3 dimension = volume.GetExtents();
  136. AZ::Transform pose = AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateIdentity(), volume.GetCenter());
  137. Physics::BoxShapeConfiguration shapeConfiguration;
  138. shapeConfiguration.m_dimensions = dimension;
  139. AzPhysics::OverlapRequest request = AzPhysics::OverlapRequestHelpers::CreateBoxOverlapRequest(dimension, pose, nullptr);
  140. request.m_queryType = AzPhysics::SceneQuery::QueryType::Static; // only looking for static PhysX colliders
  141. request.m_collisionGroup = m_collisionGroup;
  142. AzPhysics::SceneQuery::UnboundedOverlapHitCallback unboundedOverlapHitCallback =
  143. [&overlapHits](AZStd::optional<AzPhysics::SceneQueryHit>&& hit)
  144. {
  145. if (hit && ((hit->m_resultFlags & AzPhysics::SceneQuery::EntityId) != 0))
  146. {
  147. const AzPhysics::SceneQueryHit& sceneQueryHit = *hit;
  148. overlapHits.push_back(sceneQueryHit);
  149. }
  150. return true;
  151. };
  152. //! We need to use unbounded callback, otherwise the results will be limited to 32 or so objects.
  153. request.m_unboundedOverlapHitCallback = unboundedOverlapHitCallback;
  154. if (auto sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
  155. {
  156. AzPhysics::SceneHandle sceneHandle = sceneInterface->GetSceneHandle(GetSceneName());
  157. // Note: blocking call
  158. sceneInterface->QueryScene(sceneHandle, &request);
  159. // results are in overlapHits
  160. }
  161. }
  162. void RecastNavigationPhysXProviderComponentController::AppendColliderGeometry(
  163. TileGeometry& geometry,
  164. const QueryHits& overlapHits)
  165. {
  166. AZ_PROFILE_SCOPE(Navigation, "Navigation: AppendColliderGeometry");
  167. AZStd::vector<AZ::Vector3> vertices;
  168. AZStd::vector<AZ::u32> indices;
  169. AZStd::size_t indicesCount = geometry.m_indices.size();
  170. AzPhysics::SceneInterface* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get();
  171. AzPhysics::SceneHandle sceneHandle = sceneInterface->GetSceneHandle(GetSceneName());
  172. for (const auto& overlapHit : overlapHits)
  173. {
  174. AzPhysics::SimulatedBody* body = sceneInterface->GetSimulatedBodyFromHandle(sceneHandle, overlapHit.m_bodyHandle);
  175. if (!body)
  176. {
  177. continue;
  178. }
  179. // Create an AABB for the Recast tile in local space and pass it in to GetGeometry so that large geometry sets
  180. // (like heightfields) can just return the subset of geometry that overlaps the AABB.
  181. auto pose = overlapHit.m_shape->GetLocalPose();
  182. AZ::Aabb localScanBounds = geometry.m_scanBounds.GetTranslated(-pose.first);
  183. overlapHit.m_shape->GetGeometry(vertices, indices, &localScanBounds);
  184. // Note: returned geometry data is also in local space
  185. AZ::Transform tBody = AZ::Transform::CreateFromQuaternionAndTranslation(body->GetOrientation(), body->GetPosition());
  186. AZ::Transform t = tBody * AZ::Transform::CreateTranslation(pose.first);
  187. if (!vertices.empty())
  188. {
  189. if (indices.empty())
  190. {
  191. // Some PhysX colliders (convex shapes) return geometry without indices. Build indices now.
  192. AZStd::vector<AZ::Vector3> transformed;
  193. int currentLocalIndex = 0;
  194. for (const AZ::Vector3& vertex : vertices)
  195. {
  196. const AZ::Vector3 translated = t.TransformPoint(vertex);
  197. geometry.m_vertices.push_back(RecastVector3::CreateFromVector3SwapYZ(translated));
  198. geometry.m_indices.push_back(aznumeric_cast<AZ::u32>(indicesCount + currentLocalIndex));
  199. currentLocalIndex++;
  200. }
  201. }
  202. else
  203. {
  204. AZStd::vector<AZ::Vector3> transformed;
  205. for (const AZ::Vector3& vertex : vertices)
  206. {
  207. const AZ::Vector3 translated = t.TransformPoint(vertex);
  208. geometry.m_vertices.push_back(RecastVector3::CreateFromVector3SwapYZ(translated));
  209. }
  210. for (size_t i = 2; i < indices.size(); i += 3)
  211. {
  212. geometry.m_indices.push_back(aznumeric_cast<AZ::u32>(indicesCount + indices[i]));
  213. geometry.m_indices.push_back(aznumeric_cast<AZ::u32>(indicesCount + indices[i - 1]));
  214. geometry.m_indices.push_back(aznumeric_cast<AZ::u32>(indicesCount + indices[i - 2]));
  215. }
  216. }
  217. indicesCount += vertices.size();
  218. vertices.clear();
  219. indices.clear();
  220. }
  221. }
  222. }
  223. AZStd::vector<AZStd::shared_ptr<TileGeometry>> RecastNavigationPhysXProviderComponentController::CollectGeometryImpl(
  224. float tileSize, float borderSize, const AZ::Aabb& worldVolume)
  225. {
  226. AZ_PROFILE_SCOPE(Navigation, "Navigation: CollectGeometry");
  227. bool notInProgress = false;
  228. if (!m_updateInProgress.compare_exchange_strong(notInProgress, true))
  229. {
  230. return {};
  231. }
  232. if (tileSize <= 0.f)
  233. {
  234. return {};
  235. }
  236. AZStd::vector<AZStd::shared_ptr<TileGeometry>> tiles;
  237. const AZ::Vector3 extents = worldVolume.GetExtents();
  238. int tilesAlongX = aznumeric_cast<int>(AZStd::ceil(extents.GetX() / tileSize));
  239. int tilesAlongY = aznumeric_cast<int>(AZStd::ceil(extents.GetY() / tileSize));
  240. const AZ::Vector3& worldMin = worldVolume.GetMin();
  241. const AZ::Vector3& worldMax = worldVolume.GetMax();
  242. const AZ::Vector3 border = AZ::Vector3::CreateOne() * borderSize;
  243. // Find all geometry one tile at a time.
  244. for (int y = 0; y < tilesAlongY; ++y)
  245. {
  246. for (int x = 0; x < tilesAlongX; ++x)
  247. {
  248. const AZ::Vector3 tileMin{
  249. worldMin.GetX() + aznumeric_cast<float>(x) * tileSize,
  250. worldMin.GetY() + aznumeric_cast<float>(y) * tileSize,
  251. worldMin.GetZ()
  252. };
  253. const AZ::Vector3 tileMax{
  254. worldMin.GetX() + aznumeric_cast<float>(x + 1) * tileSize,
  255. worldMin.GetY() + aznumeric_cast<float>(y + 1) * tileSize,
  256. worldMax.GetZ()
  257. };
  258. // Recast wants extra triangle data around each tile, so that each tile can connect to each other.
  259. AZ::Aabb tileVolume = AZ::Aabb::CreateFromMinMax(tileMin, tileMax);
  260. AZ::Aabb scanVolume = AZ::Aabb::CreateFromMinMax(tileMin - border, tileMax + border);
  261. QueryHits results;
  262. CollectCollidersWithinVolume(scanVolume, results);
  263. AZStd::shared_ptr<TileGeometry> geometryData = AZStd::make_unique<TileGeometry>();
  264. geometryData->m_worldBounds = tileVolume;
  265. geometryData->m_scanBounds = scanVolume;
  266. AppendColliderGeometry(*geometryData, results);
  267. geometryData->m_tileX = x;
  268. geometryData->m_tileY = y;
  269. tiles.push_back(geometryData);
  270. }
  271. }
  272. m_updateInProgress = false;
  273. return tiles;
  274. }
  275. // Adjust the origin, so that any tile over-extension is even across all sides.
  276. // Note, navigation mesh is made up of square tiles. Recast does not support uneven tiles,
  277. // so the best we can do is even them out. Additionally, users can set their own tile size on @RecastNavigationMeshComponent.
  278. AZ::Vector3 GetAdjustedOriginBasedOnTileSize(const AZ::Aabb& worldVolume, float tileSize)
  279. {
  280. if (tileSize <= 0.f)
  281. {
  282. AZ_Warning("Recast Navigation", true, "Tile size is invalid. It should be a positive number.");
  283. return AZ::Vector3::CreateZero();
  284. }
  285. AZ::Vector3 origin = worldVolume.GetMin();
  286. const AZ::Vector3& extents = worldVolume.GetExtents();
  287. const float tileOverExtensionOnX = AZStd::ceil(extents.GetX() / tileSize) * tileSize - extents.GetX();
  288. origin.SetX(origin.GetX() - tileOverExtensionOnX / 2.f);
  289. const float tileOverExtensionOnY = AZStd::ceil(extents.GetY() / tileSize) * tileSize - extents.GetY();
  290. origin.SetY(origin.GetY() - tileOverExtensionOnY / 2.f);
  291. return origin;
  292. }
  293. bool RecastNavigationPhysXProviderComponentController::CollectGeometryAsyncImpl(
  294. float tileSize,
  295. float borderSize,
  296. const AZ::Aabb& worldVolume,
  297. AZStd::function<void(AZStd::shared_ptr<TileGeometry>)> tileCallback)
  298. {
  299. bool notInProgress = false;
  300. if (!m_updateInProgress.compare_exchange_strong(notInProgress, true))
  301. {
  302. return false;
  303. }
  304. if (!m_taskGraphEvent || m_taskGraphEvent->IsSignaled())
  305. {
  306. AZ_PROFILE_SCOPE(Navigation, "Navigation: CollectGeometryAsync");
  307. m_taskGraphEvent = AZStd::make_unique<AZ::TaskGraphEvent>("RecastNavigation PhysX Wait");
  308. m_taskGraph.Reset();
  309. AZStd::vector<AZStd::shared_ptr<TileGeometry>> tiles;
  310. const AZ::Vector3 extents = worldVolume.GetExtents();
  311. int tilesAlongX = aznumeric_cast<int>(AZStd::ceil(extents.GetX() / tileSize));
  312. int tilesAlongY = aznumeric_cast<int>(AZStd::ceil(extents.GetY() / tileSize));
  313. const AZ::Vector3 worldOrigin = GetAdjustedOriginBasedOnTileSize(worldVolume, tileSize);
  314. const AZ::Vector3 worldMax(
  315. worldOrigin.GetX() + tileSize * aznumeric_cast<float>(tilesAlongX),
  316. worldOrigin.GetY() + tileSize * aznumeric_cast<float>(tilesAlongY),
  317. worldVolume.GetMax().GetZ());
  318. const AZ::Vector3 border = AZ::Vector3::CreateOne() * borderSize;
  319. AZStd::vector<AZ::TaskToken> tileTaskTokens;
  320. // Create tasks for each tile and a finish task.
  321. for (int y = 0; y < tilesAlongY; ++y)
  322. {
  323. for (int x = 0; x < tilesAlongX; ++x)
  324. {
  325. const AZ::Vector3 tileMin{
  326. worldOrigin.GetX() + aznumeric_cast<float>(x) * tileSize,
  327. worldOrigin.GetY() + aznumeric_cast<float>(y) * tileSize,
  328. worldOrigin.GetZ()
  329. };
  330. const AZ::Vector3 tileMax{
  331. worldOrigin.GetX() + aznumeric_cast<float>(x + 1) * tileSize,
  332. worldOrigin.GetY() + aznumeric_cast<float>(y + 1) * tileSize,
  333. worldMax.GetZ()
  334. };
  335. AZ::Aabb tileVolume = AZ::Aabb::CreateFromMinMax(tileMin, tileMax);
  336. AZ::Aabb scanVolume = AZ::Aabb::CreateFromMinMax(tileMin - border, tileMax + border);
  337. AZStd::shared_ptr<TileGeometry> geometryData = AZStd::make_unique<TileGeometry>();
  338. geometryData->m_tileCallback = tileCallback;
  339. geometryData->m_worldBounds = tileVolume;
  340. geometryData->m_scanBounds = scanVolume;
  341. geometryData->m_tileX = x;
  342. geometryData->m_tileY = y;
  343. AZ::TaskToken token = m_taskGraph.AddTask(
  344. m_taskDescriptor, [this, geometryData]()
  345. {
  346. if (m_shouldProcessTiles)
  347. {
  348. AZ_PROFILE_SCOPE(Navigation, "Navigation: collecting geometry for a tile");
  349. QueryHits results;
  350. CollectCollidersWithinVolume(geometryData->m_scanBounds, results);
  351. AppendColliderGeometry(*geometryData, results);
  352. geometryData->m_tileCallback(geometryData);
  353. }
  354. });
  355. tileTaskTokens.push_back(AZStd::move(token));
  356. }
  357. }
  358. AZ::TaskToken finishToken = m_taskGraph.AddTask(
  359. m_taskDescriptor, [this, tileCallback]()
  360. {
  361. tileCallback({}); // Notifies the caller that the operation is done.
  362. m_updateInProgress = false;
  363. });
  364. for (AZ::TaskToken& task : tileTaskTokens)
  365. {
  366. task.Precedes(finishToken);
  367. }
  368. AZ_Assert(m_taskGraphEvent->IsSignaled() == false, "RecastNavigationPhysXProviderComponentController might be runtime two async gather operations, which is not supported.");
  369. m_taskGraph.SubmitOnExecutor(m_taskExecutor, m_taskGraphEvent.get());
  370. return true;
  371. }
  372. return false;
  373. }
  374. } // namespace RecastNavigation