3
0

TerrainWorldDebuggerComponent.cpp 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  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 <Components/TerrainWorldDebuggerComponent.h>
  9. #include <AzCore/Asset/AssetManager.h>
  10. #include <AzCore/Asset/AssetManagerBus.h>
  11. #include <AzCore/Component/Entity.h>
  12. #include <AzCore/RTTI/BehaviorContext.h>
  13. #include <AzCore/Serialization/EditContext.h>
  14. #include <AzCore/Serialization/SerializeContext.h>
  15. #include <AzFramework/Visibility/EntityBoundsUnionBus.h>
  16. #include <Atom/RPI.Public/View.h>
  17. #include <Atom/RPI.Public/ViewportContext.h>
  18. #include <Atom/RPI.Public/ViewportContextBus.h>
  19. AZ_DECLARE_BUDGET(Terrain);
  20. namespace Terrain
  21. {
  22. void TerrainWorldDebuggerConfig::Reflect(AZ::ReflectContext* context)
  23. {
  24. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  25. if (serialize)
  26. {
  27. serialize->Class<TerrainDebugQueryVisualizerConfig>()
  28. ->Version(1)
  29. ->Field("DrawQueries", &TerrainDebugQueryVisualizerConfig::m_drawQueries)
  30. ->Field("Sampler", &TerrainDebugQueryVisualizerConfig::m_sampler)
  31. ->Field("PointsPerDirection", &TerrainDebugQueryVisualizerConfig::m_pointsPerDirection)
  32. ->Field("Spacing", &TerrainDebugQueryVisualizerConfig::m_spacing)
  33. ->Field("DrawHeights", &TerrainDebugQueryVisualizerConfig::m_drawHeights)
  34. ->Field("HeightPointSize", &TerrainDebugQueryVisualizerConfig::m_heightPointSize)
  35. ->Field("DrawNormals", &TerrainDebugQueryVisualizerConfig::m_drawNormals)
  36. ->Field("NormalHeight", &TerrainDebugQueryVisualizerConfig::m_normalHeight)
  37. ->Field("UseCameraPos", &TerrainDebugQueryVisualizerConfig::m_useCameraPosition)
  38. ->Field("CenterPos", &TerrainDebugQueryVisualizerConfig::m_centerPosition)
  39. ;
  40. serialize->Class<TerrainWorldDebuggerConfig, AZ::ComponentConfig>()
  41. ->Version(2)
  42. ->Field("DebugWireframe", &TerrainWorldDebuggerConfig::m_drawWireframe)
  43. ->Field("DebugWorldBounds", &TerrainWorldDebuggerConfig::m_drawWorldBounds)
  44. ->Field("DebugDirtyRegion", &TerrainWorldDebuggerConfig::m_drawLastDirtyRegion)
  45. ->Field("DebugQueries", &TerrainWorldDebuggerConfig::m_debugQueries)
  46. ;
  47. AZ::EditContext* edit = serialize->GetEditContext();
  48. if (edit)
  49. {
  50. edit->Class<TerrainDebugQueryVisualizerConfig>(
  51. "Terrain Debug Queries", "Settings related to visualizing the results of terrain queries.")
  52. ->GroupElementToggle("Show Terrain Queries", &TerrainDebugQueryVisualizerConfig::m_drawQueries)
  53. ->Attribute(AZ::Edit::Attributes::AutoExpand, false)
  54. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
  55. ->DataElement(AZ::Edit::UIHandlers::ComboBox, &TerrainDebugQueryVisualizerConfig::m_sampler, "Sampler",
  56. "The type of query sampler to use for querying the terrain values (Exact, Clamp, Bilinear)")
  57. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainDebugQueryVisualizerConfig::DrawQueriesDisabled)
  58. ->EnumAttribute(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, "Exact")
  59. ->EnumAttribute(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP, "Clamp")
  60. ->EnumAttribute(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR, "Bilinear")
  61. ->DataElement(AZ::Edit::UIHandlers::Slider, &TerrainDebugQueryVisualizerConfig::m_pointsPerDirection, "Point count",
  62. "The number of points in each direction to visualize")
  63. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainDebugQueryVisualizerConfig::DrawQueriesDisabled)
  64. ->Attribute(AZ::Edit::Attributes::Min, 1)
  65. ->Attribute(AZ::Edit::Attributes::Max, 64)
  66. ->DataElement(AZ::Edit::UIHandlers::Slider, &TerrainDebugQueryVisualizerConfig::m_spacing, "Spacing (m)",
  67. "Determines how far apart the query results should be drawn in meters")
  68. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainDebugQueryVisualizerConfig::DrawQueriesDisabled)
  69. ->Attribute(AZ::Edit::Attributes::Min, 0.001f)
  70. ->Attribute(AZ::Edit::Attributes::SoftMin, 0.25f)
  71. ->Attribute(AZ::Edit::Attributes::SoftMax, 4.0f)
  72. ->Attribute(AZ::Edit::Attributes::Max, 10000.0f)
  73. ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainDebugQueryVisualizerConfig::m_drawHeights, "Draw Heights",
  74. "Enable visualization of terrain height queries")
  75. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainDebugQueryVisualizerConfig::DrawQueriesDisabled)
  76. ->DataElement(AZ::Edit::UIHandlers::Slider, &TerrainDebugQueryVisualizerConfig::m_heightPointSize,
  77. "Height Point Size (m)", "Determines the size of the height point in meters")
  78. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainDebugQueryVisualizerConfig::DisableHeights)
  79. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  80. ->Attribute(AZ::Edit::Attributes::SoftMin, 1.0f / 128.0f)
  81. ->Attribute(AZ::Edit::Attributes::SoftMax, 4.0f)
  82. ->Attribute(AZ::Edit::Attributes::Max, 10000.0f)
  83. ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainDebugQueryVisualizerConfig::m_drawNormals, "Draw Normals",
  84. "Enable visualization of terrain normal queries")
  85. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainDebugQueryVisualizerConfig::DrawQueriesDisabled)
  86. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
  87. ->DataElement(AZ::Edit::UIHandlers::Slider, &TerrainDebugQueryVisualizerConfig::m_normalHeight, "Normal Height (m)",
  88. "Determines the height of the normal line in meters")
  89. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainDebugQueryVisualizerConfig::DisableNormals)
  90. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  91. ->Attribute(AZ::Edit::Attributes::SoftMin, 0.25f)
  92. ->Attribute(AZ::Edit::Attributes::SoftMax, 16.0f)
  93. ->Attribute(AZ::Edit::Attributes::Max, 10000.0f)
  94. ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainDebugQueryVisualizerConfig::m_useCameraPosition, "Use Camera Position",
  95. "Determines whether to use the current camera position or a specified position")
  96. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainDebugQueryVisualizerConfig::DrawQueriesDisabled)
  97. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
  98. ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainDebugQueryVisualizerConfig::m_centerPosition, "World Position",
  99. "Center of the area to draw query results in")
  100. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainDebugQueryVisualizerConfig::DisableCenterPosition)
  101. ;
  102. edit->Class<TerrainWorldDebuggerConfig>(
  103. "Terrain World Debugger Component", "Optional component for enabling terrain debugging features.")
  104. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  105. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector<AZ::Crc32>({ AZ_CRC_CE("Level") }))
  106. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  107. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  108. ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldDebuggerConfig::m_drawWireframe, "Show Wireframe",
  109. "Draw a wireframe for the terrain quads in an area around the camera")
  110. ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldDebuggerConfig::m_drawWorldBounds, "Show World Bounds",
  111. "Draw the current world bounds for the terrain")
  112. ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldDebuggerConfig::m_drawLastDirtyRegion,
  113. "Show Dirty Region", "Draw the most recent dirty region for the terrain")
  114. ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldDebuggerConfig::m_debugQueries, "Show Normals",
  115. "Settings for drawing terrain normals")
  116. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  117. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  118. ;
  119. }
  120. }
  121. }
  122. void TerrainWorldDebuggerComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  123. {
  124. services.push_back(AZ_CRC_CE("TerrainDebugService"));
  125. }
  126. void TerrainWorldDebuggerComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  127. {
  128. services.push_back(AZ_CRC_CE("TerrainDebugService"));
  129. }
  130. void TerrainWorldDebuggerComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  131. {
  132. services.push_back(AZ_CRC_CE("TerrainService"));
  133. }
  134. void TerrainWorldDebuggerComponent::Reflect(AZ::ReflectContext* context)
  135. {
  136. TerrainWorldDebuggerConfig::Reflect(context);
  137. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  138. if (serialize)
  139. {
  140. serialize->Class<TerrainWorldDebuggerComponent, AZ::Component>()
  141. ->Version(0)
  142. ->Field("Configuration", &TerrainWorldDebuggerComponent::m_configuration)
  143. ;
  144. }
  145. }
  146. TerrainWorldDebuggerComponent::TerrainWorldDebuggerComponent(const TerrainWorldDebuggerConfig& configuration)
  147. : m_configuration(configuration)
  148. {
  149. }
  150. TerrainWorldDebuggerComponent::~TerrainWorldDebuggerComponent()
  151. {
  152. }
  153. void TerrainWorldDebuggerComponent::Activate()
  154. {
  155. // Given the AuxGeom vertex limits, MaxSectorsToDraw is the max number of wireframe sectors we can draw without exceeding the
  156. // limits. Since we want an N x N sector grid, take the square root to get the number of sectors in each direction.
  157. m_sectorGridSize = aznumeric_cast<int32_t>(sqrtf(MaxSectorsToDraw));
  158. // We're always going to keep the camera in the center square, so "round" downwards to an odd number of sectors if we currently
  159. // have an even number. (If we added a sector, we'll go above the max sectors that we can draw with our vertex limits)
  160. m_sectorGridSize = (m_sectorGridSize & 0x01) ? m_sectorGridSize : m_sectorGridSize - 1;
  161. // Create our fixed set of sectors that we'll draw. By default, they'll all be constructed as dirty, so they'll get refreshed
  162. // the first time we try to draw them. (If wireframe drawing is disabled, we'll never refresh them)
  163. m_wireframeSectors.clear();
  164. m_wireframeSectors.resize(m_sectorGridSize * m_sectorGridSize);
  165. AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(GetEntityId());
  166. AzFramework::BoundsRequestBus::Handler::BusConnect(GetEntityId());
  167. AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect();
  168. // Any time the world bounds potentially changes, notify that the terrain debugger's visibility bounds also changed.
  169. // Otherwise, DisplayEntityViewport() won't get called at the appropriate times, since the visibility could get incorrectly
  170. // culled out.
  171. AzFramework::IEntityBoundsUnionRequestBus::Broadcast(
  172. &AzFramework::IEntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId());
  173. }
  174. void TerrainWorldDebuggerComponent::Deactivate()
  175. {
  176. AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect();
  177. AzFramework::BoundsRequestBus::Handler::BusDisconnect();
  178. AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect();
  179. m_wireframeSectors.clear();
  180. }
  181. bool TerrainWorldDebuggerComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
  182. {
  183. if (auto config = azrtti_cast<const TerrainWorldDebuggerConfig*>(baseConfig))
  184. {
  185. m_configuration = *config;
  186. return true;
  187. }
  188. return false;
  189. }
  190. bool TerrainWorldDebuggerComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
  191. {
  192. if (auto config = azrtti_cast<TerrainWorldDebuggerConfig*>(outBaseConfig))
  193. {
  194. *config = m_configuration;
  195. return true;
  196. }
  197. return false;
  198. }
  199. AZ::Aabb TerrainWorldDebuggerComponent::GetWorldBounds() const
  200. {
  201. AZ::Aabb terrainAabb = AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero());
  202. AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
  203. terrainAabb, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
  204. return terrainAabb;
  205. }
  206. AZ::Aabb TerrainWorldDebuggerComponent::GetLocalBounds() const
  207. {
  208. // This is a level component, so the local bounds will always be the same as the world bounds.
  209. return GetWorldBounds();
  210. }
  211. void TerrainWorldDebuggerComponent::MarkDirtySectors(const AZ::Aabb& dirtyRegion)
  212. {
  213. // Create a 2D version of dirtyRegion that has Z set to min/max float values, so that we can just check for XY overlap with
  214. // each sector.
  215. const AZ::Aabb dirtyRegion2D = AZ::Aabb::CreateFromMinMaxValues(
  216. dirtyRegion.GetMin().GetX(), dirtyRegion.GetMin().GetY(), AZStd::numeric_limits<float>::lowest(),
  217. dirtyRegion.GetMax().GetX(), dirtyRegion.GetMax().GetY(), AZStd::numeric_limits<float>::max());
  218. // For each sector that overlaps the dirty region (or all of them if the region is invalid), mark them as dirty so that
  219. // they'll get refreshed the next time we need to draw them.
  220. for (auto& sector : m_wireframeSectors)
  221. {
  222. AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
  223. if (!dirtyRegion2D.IsValid() || dirtyRegion2D.Overlaps(sector.m_aabb))
  224. {
  225. sector.SetDirty();
  226. }
  227. }
  228. }
  229. void TerrainWorldDebuggerComponent::DrawLastDirtyRegion(AzFramework::DebugDisplayRequests& debugDisplay)
  230. {
  231. using DataChangedMask = AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask;
  232. if (!m_configuration.m_drawLastDirtyRegion)
  233. {
  234. return;
  235. }
  236. // Draw a translucent box around the terrain dirty region
  237. const AZ::Color dirtyRegionColor(
  238. (m_lastDirtyData & (DataChangedMask::HeightData | DataChangedMask::Settings)) != DataChangedMask::None ? 1.0f : 0.0f,
  239. (m_lastDirtyData & (DataChangedMask::SurfaceData | DataChangedMask::Settings)) != DataChangedMask::None ? 1.0f : 0.0f,
  240. (m_lastDirtyData & (DataChangedMask::ColorData | DataChangedMask::Settings)) != DataChangedMask::None ? 1.0f : 0.0f,
  241. 0.25f);
  242. if (m_lastDirtyRegion.IsValid())
  243. {
  244. debugDisplay.SetColor(dirtyRegionColor);
  245. debugDisplay.DrawSolidBox(m_lastDirtyRegion.GetMin(), m_lastDirtyRegion.GetMax());
  246. }
  247. }
  248. void TerrainWorldDebuggerComponent::DrawWorldBounds(AzFramework::DebugDisplayRequests& debugDisplay)
  249. {
  250. if (!m_configuration.m_drawWorldBounds)
  251. {
  252. return;
  253. }
  254. // Draw a wireframe box around the entire terrain world bounds
  255. const AZ::Color outlineColor(1.0f, 0.0f, 0.0f, 1.0f);
  256. const AZ::Aabb aabb = GetWorldBounds();
  257. if (aabb.IsValid())
  258. {
  259. debugDisplay.SetColor(outlineColor);
  260. debugDisplay.DrawWireBox(aabb.GetMin(), aabb.GetMax());
  261. }
  262. }
  263. void TerrainWorldDebuggerComponent::DrawQueries(
  264. const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
  265. {
  266. AZ_PROFILE_FUNCTION(Terrain);
  267. if (!m_configuration.m_debugQueries.m_drawQueries)
  268. {
  269. return;
  270. }
  271. // Early out if none of our draw toggles are enabled.
  272. if (!(m_configuration.m_debugQueries.m_drawHeights || m_configuration.m_debugQueries.m_drawNormals))
  273. {
  274. return;
  275. }
  276. const float spacing = m_configuration.m_debugQueries.m_spacing;
  277. const float halfDistance = spacing * (m_configuration.m_debugQueries.m_pointsPerDirection / 2.0f);
  278. const size_t totalPositions =
  279. m_configuration.m_debugQueries.m_pointsPerDirection * m_configuration.m_debugQueries.m_pointsPerDirection;
  280. // Get the center point for our visualization area either from the camera or a configured location.
  281. AZ::Vector3 centerPos = m_configuration.m_debugQueries.m_centerPosition;
  282. if (m_configuration.m_debugQueries.m_useCameraPosition)
  283. {
  284. if (auto viewportContextRequests = AZ::RPI::ViewportContextRequests::Get(); viewportContextRequests)
  285. {
  286. AZ::RPI::ViewportContextPtr viewportContext = viewportContextRequests->GetViewportContextById(viewportInfo.m_viewportId);
  287. const AZ::Transform cameraTransform = viewportContext->GetCameraTransform();
  288. // Get our camera's center point
  289. centerPos = cameraTransform.GetTranslation();
  290. }
  291. }
  292. // Build up the list of positions to query.
  293. AZStd::vector<AZ::Vector3> positionList;
  294. positionList.reserve(totalPositions);
  295. for (size_t yPoint = 0; yPoint < m_configuration.m_debugQueries.m_pointsPerDirection; yPoint++)
  296. {
  297. for (size_t xPoint = 0; xPoint < m_configuration.m_debugQueries.m_pointsPerDirection; xPoint++)
  298. {
  299. float x = centerPos.GetX() - halfDistance + (spacing * xPoint);
  300. float y = centerPos.GetY() - halfDistance + (spacing * yPoint);
  301. positionList.emplace_back(x, y, 0.0f);
  302. }
  303. }
  304. // Process the terrain data query and turn the results into debug visualizations.
  305. // We'll reuse the normalLineVertices buffer for drawing both heights and normals. The first point of each normal line
  306. // always starts at the height position, so we can use those to draw heights.
  307. const float normalHeight = m_configuration.m_debugQueries.m_normalHeight;
  308. AZStd::vector<AZ::Vector3> normalLineVertices;
  309. normalLineVertices.reserve(totalPositions * 2);
  310. auto ProcessSurfacePoint =
  311. [normalHeight, &normalLineVertices](const AzFramework::SurfaceData::SurfacePoint& surfacePoint, bool terrainExists)
  312. {
  313. if (terrainExists)
  314. {
  315. normalLineVertices.emplace_back(surfacePoint.m_position);
  316. normalLineVertices.emplace_back(surfacePoint.m_position + (surfacePoint.m_normal * normalHeight));
  317. }
  318. };
  319. // Query for the terrain data. For now we'll just query both heights and normals all the time, but we could eventually
  320. // get more selective and only query for heights if we've disabled drawing normals. We can never query just for normals
  321. // because we still need the heights in that case to know where to draw the normals at.
  322. AzFramework::Terrain::TerrainDataRequestBus::Broadcast(
  323. &AzFramework::Terrain::TerrainDataRequests::QueryList,
  324. positionList,
  325. static_cast<AzFramework::Terrain::TerrainDataRequests::TerrainDataMask>(
  326. AzFramework::Terrain::TerrainDataRequests::TerrainDataMask::Heights |
  327. AzFramework::Terrain::TerrainDataRequests::TerrainDataMask::Normals),
  328. ProcessSurfacePoint,
  329. m_configuration.m_debugQueries.m_sampler);
  330. // Draw the heights
  331. if (m_configuration.m_debugQueries.m_drawHeights && !normalLineVertices.empty())
  332. {
  333. const AZ::Color heightColor = AZ::Color(0.0f, 0.0f, 1.0f, 1.0f);
  334. const AZ::Vector3 boxHalfSize(m_configuration.m_debugQueries.m_heightPointSize / 2.0f);
  335. debugDisplay.SetColor(heightColor);
  336. for (size_t index = 0; index < normalLineVertices.size(); index += 2)
  337. {
  338. // We use SolidBox instead of Point because DX12 doesn't support point sizes, so they're too small to see.
  339. debugDisplay.DrawSolidBox(normalLineVertices[index] - boxHalfSize, normalLineVertices[index] + boxHalfSize);
  340. }
  341. }
  342. // Draw the normals
  343. if (m_configuration.m_debugQueries.m_drawNormals && !normalLineVertices.empty())
  344. {
  345. for (size_t index = 0; index < normalLineVertices.size(); index += 2)
  346. {
  347. AZ::Vector3 normal = (normalLineVertices[index + 1] - normalLineVertices[index]).GetNormalized();
  348. const AZ::Vector4 normalColor = (AZ::Vector4(normal, 1.0f) + AZ::Vector4(1.0f)) / 2.0f;
  349. debugDisplay.DrawLine(normalLineVertices[index], normalLineVertices[index + 1], normalColor, normalColor);
  350. }
  351. }
  352. }
  353. void TerrainWorldDebuggerComponent::DrawWireframe(
  354. const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
  355. {
  356. AZ_PROFILE_FUNCTION(Terrain);
  357. if (!m_configuration.m_drawWireframe)
  358. {
  359. return;
  360. }
  361. /* This draws a wireframe centered on the camera that extends out to a certain distance at all times. To reduce the amount of
  362. * recalculations we need to do on each camera movement, we divide the world into a conceptual grid of sectors, where each sector
  363. * contains a fixed number of terrain height points. So for example, if the terrain has height data at 1 m spacing, the sectors
  364. * might be 10 m x 10 m in size. If the height data is spaced at 0.5 m, the sectors might be 5 m x 5 m in size. The wireframe
  365. * draws N x N sectors centered around the camera, as determined by m_sectorGridSize. So a gridSize of 7 with a sector size of
  366. * 10 m means that we'll be drawing 7 x 7 sectors, or 70 m x 70 m, centered around the camera. Each time the camera moves into
  367. * a new sector, we refresh the changed sectors before drawing them.
  368. *
  369. * The only tricky bit to this design is the way the sectors are stored and indexed. They're stored in a single vector as NxN
  370. * entries, so they would normally be indexed as (y * N) + x. Since we want this to be centered on the camera, the easy answer
  371. * would be to take the camera position - (N / 2) (since we're centering) as the relative offset to the first entry. But this
  372. * would mean that the entire set of entries would change every time we move the camera. For example, if we had 5 entries,
  373. * they might map to 0-4, 1-5, 2-6, 3-7, etc as the camera moves.
  374. *
  375. * Instead, we use mod (%) to rotate our indices around, so it would go (0 1 2 3 4), (5 1 2 3 4), (5 6 2 3 4), (5 6 7 3 4), etc
  376. * as the camera moves. For negative entries, we rotate the indices in reverse, so that we get results like (0 1 2 3 4),
  377. * (0 1 2 3 -1), (0 1 2 -2 -1), (0 1 -3 -2 -1), etc. This way we always have the correct range of sectors, and sectors that have
  378. * remained visible are left alone and don't need to be updated again.
  379. */
  380. // Get the terrain world bounds
  381. AZ::Aabb worldBounds = GetWorldBounds();
  382. float worldMinZ = worldBounds.GetMin().GetZ();
  383. // Get the terrain height data resolution
  384. float heightDataResolution = 1.0f;
  385. AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
  386. heightDataResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution);
  387. // Get the size of a wireframe sector in world space
  388. const AZ::Vector2 sectorSize = AZ::Vector2(heightDataResolution * SectorSizeInGridPoints);
  389. // Try to get the current camera position, or default to (0,0) if we can't.
  390. AZ::Vector3 cameraPos = AZ::Vector3::CreateZero();
  391. if (auto viewportContextRequests = AZ::RPI::ViewportContextRequests::Get(); viewportContextRequests)
  392. {
  393. AZ::RPI::ViewportContextPtr viewportContext = viewportContextRequests->GetViewportContextById(viewportInfo.m_viewportId);
  394. cameraPos = viewportContext->GetCameraTransform().GetTranslation();
  395. }
  396. // Convert our camera position to a wireframe grid sector. We first convert from world space to sector space by dividing by
  397. // sectorSize, so that integer values are sectors, and fractional values are the distance within the sector. Then we get the
  398. // floor, so that we consistently get the next lowest integer - i.e. 2.3 -> 2, and -2.3 -> -3. This gives us consistent behavior
  399. // across both positive and negative positions.
  400. AZ::Vector2 gridPosition = AZ::Vector2(cameraPos.GetX(), cameraPos.GetY()) / sectorSize;
  401. int32_t cameraSectorX = aznumeric_cast<int32_t>(gridPosition.GetFloor().GetX());
  402. int32_t cameraSectorY = aznumeric_cast<int32_t>(gridPosition.GetFloor().GetY());
  403. // Loop through each sector that we *want* to draw, based on camera position. If the current sector at that index in
  404. // m_wireframeSectors doesn't match the world position we want, update its world position and mark it as dirty.
  405. // (We loop from -gridSize/2 to gridSize/2 so that the camera is always in the center sector.)
  406. for (int32_t sectorY = cameraSectorY - (m_sectorGridSize / 2); sectorY <= cameraSectorY + (m_sectorGridSize / 2); sectorY++)
  407. {
  408. for (int32_t sectorX = cameraSectorX - (m_sectorGridSize / 2); sectorX <= cameraSectorX + (m_sectorGridSize / 2); sectorX++)
  409. {
  410. // Calculate the index in m_wireframeSectors for this sector. Our indices should rotate through 0 - gridSize, but just
  411. // using a single mod will produce a negative result for negative sector indices. Using abs() will give us incorrect
  412. // "backwards" indices for negative numbers, so instead we add the grid size and mod a second time.
  413. // Ex: For a grid size of 5, we want the indices to map like this:
  414. // Index 0 1 2 3 4
  415. // Values -10 -9 -8 -7 -6
  416. // -5 -4 -3 -2 -1
  417. // 0 1 2 3 4
  418. // 5 6 7 8 9
  419. // For -9, (-9 % 5) = -4, then (-4 + 5) % 5 = 1. If we used abs(), we'd get 4, which is backwards from what we want.
  420. int32_t sectorYIndex = ((sectorY % m_sectorGridSize) + m_sectorGridSize) % m_sectorGridSize;
  421. int32_t sectorXIndex = ((sectorX % m_sectorGridSize) + m_sectorGridSize) % m_sectorGridSize;
  422. int32_t sectorIndex = (sectorYIndex * m_sectorGridSize) + sectorXIndex;
  423. WireframeSector& sector = m_wireframeSectors[sectorIndex];
  424. // Calculate the new world space box for this sector.
  425. AZ::Aabb sectorAabb = AZ::Aabb::CreateFromMinMax(
  426. AZ::Vector3(sectorX * sectorSize.GetX(), sectorY * sectorSize.GetY(), worldMinZ),
  427. AZ::Vector3((sectorX + 1) * sectorSize.GetX(), (sectorY + 1) * sectorSize.GetY(), worldMinZ));
  428. // If the world space box for the sector doesn't match, set it and mark the sector as dirty so we refresh the height data.
  429. {
  430. AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
  431. if (sector.m_aabb != sectorAabb)
  432. {
  433. sector.m_aabb = sectorAabb;
  434. if (worldBounds.Overlaps(sector.m_aabb))
  435. {
  436. sector.SetDirty();
  437. }
  438. else
  439. {
  440. // If this sector doesn't appear in the terrain world bounds, just clear it out.
  441. sector.m_lineVertices.clear();
  442. }
  443. }
  444. }
  445. }
  446. }
  447. // Finally, for each sector, rebuild the data if it's dirty, then draw it assuming it has valid data.
  448. // (Sectors that are outside the world bounds won't have any valid data, so they'll get skipped)
  449. for (auto& sector : m_wireframeSectors)
  450. {
  451. AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
  452. if (sector.m_jobContext)
  453. {
  454. // The previous async request for this sector has yet to complete.
  455. continue;
  456. }
  457. if (sector.m_isDirty)
  458. {
  459. RebuildSectorWireframe(sector, heightDataResolution);
  460. }
  461. else if (!sector.m_lineVertices.empty())
  462. {
  463. const AZ::Color primaryColor = AZ::Color(0.25f, 0.25f, 0.25f, 1.0f);
  464. debugDisplay.DrawLines(sector.m_lineVertices, primaryColor);
  465. }
  466. }
  467. }
  468. void TerrainWorldDebuggerComponent::DisplayEntityViewport(
  469. const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
  470. {
  471. DrawWorldBounds(debugDisplay);
  472. DrawLastDirtyRegion(debugDisplay);
  473. DrawWireframe(viewportInfo, debugDisplay);
  474. DrawQueries(viewportInfo, debugDisplay);
  475. }
  476. void TerrainWorldDebuggerComponent::RebuildSectorWireframe(WireframeSector& sector, float gridResolution)
  477. {
  478. AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
  479. if (!sector.m_isDirty)
  480. {
  481. return;
  482. }
  483. sector.m_isDirty = false;
  484. // To rebuild the wireframe for the sector, we grab all the sector vertex positions and whether or not that vertex has
  485. // terrain data that exists. _
  486. // For each point, we add two lines in a | shape. (inverted L)
  487. // We need to query one extra point in each direction so that we can get the endpoints for the final lines in each direction.
  488. AzFramework::Terrain::TerrainQueryRegion queryRegion(
  489. sector.m_aabb.GetMin(), SectorSizeInGridPoints + 1, SectorSizeInGridPoints + 1, AZ::Vector2(gridResolution));
  490. const size_t numSamplesX = queryRegion.m_numPointsX;
  491. const size_t numSamplesY = queryRegion.m_numPointsY;
  492. // We need 4 vertices for each grid point in our sector to hold the inverted L shape.
  493. sector.m_lineVertices.clear();
  494. sector.m_lineVertices.reserve(SectorSizeInGridPoints * SectorSizeInGridPoints * 4);
  495. // Clear and prepare our temporary buffers to hold all the vertex position data and "exists" flags.
  496. // (If we're multithreading, there's no guaranteed order to which each point will get filled in)
  497. sector.m_sectorVertices.clear();
  498. sector.m_sectorVertexExists.clear();
  499. sector.m_sectorVertices.resize(numSamplesX * numSamplesY);
  500. sector.m_sectorVertexExists.resize(numSamplesX * numSamplesY);
  501. // Cache off the vertex position data and "exists" flags.
  502. auto ProcessHeightValue = [numSamplesX, &sector]
  503. (size_t xIndex, size_t yIndex, const AzFramework::SurfaceData::SurfacePoint& surfacePoint, bool terrainExists)
  504. {
  505. AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
  506. if (sector.m_isDirty)
  507. {
  508. // Bail out if this sector has become dirty again since the async request started.
  509. return;
  510. }
  511. sector.m_sectorVertices[(yIndex * numSamplesX) + xIndex] = surfacePoint.m_position;
  512. sector.m_sectorVertexExists[(yIndex * numSamplesX) + xIndex] = terrainExists;
  513. };
  514. // When we've finished gathering all the height data, create all the wireframe lines.
  515. auto completionCallback =
  516. [&sector, numSamplesX, numSamplesY](AZStd::shared_ptr<AzFramework::Terrain::TerrainJobContext>)
  517. {
  518. // This must happen outside the lock, otherwise we will get a deadlock if
  519. // WireframeSector::Reset is waiting for the completion event to be signalled.
  520. sector.m_jobCompletionEvent->release();
  521. // Reset the job context once the async request has completed,
  522. // clearing the way for future requests to be made for this sector.
  523. AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
  524. sector.m_jobContext.reset();
  525. // For each vertex in the sector, try to create the inverted L shape. We'll only draw a wireframe line
  526. // if both the start and the end vertex has terrain data.
  527. for (size_t yIndex = 0; yIndex < (numSamplesY - 1); yIndex++)
  528. {
  529. for (size_t xIndex = 0; xIndex < (numSamplesX - 1); xIndex++)
  530. {
  531. size_t curIndex = (yIndex * numSamplesX) + xIndex;
  532. size_t rightIndex = (yIndex * numSamplesX) + xIndex + 1;
  533. size_t bottomIndex = ((yIndex + 1) * numSamplesX) + xIndex;
  534. if (sector.m_sectorVertexExists[curIndex] && sector.m_sectorVertexExists[bottomIndex])
  535. {
  536. sector.m_lineVertices.emplace_back(sector.m_sectorVertices[curIndex]);
  537. sector.m_lineVertices.emplace_back(sector.m_sectorVertices[bottomIndex]);
  538. }
  539. if (sector.m_sectorVertexExists[curIndex] && sector.m_sectorVertexExists[rightIndex])
  540. {
  541. sector.m_lineVertices.emplace_back(sector.m_sectorVertices[curIndex]);
  542. sector.m_lineVertices.emplace_back(sector.m_sectorVertices[rightIndex]);
  543. }
  544. }
  545. }
  546. // We're done with our temporary height buffers so clear them back out.
  547. sector.m_sectorVertices.clear();
  548. sector.m_sectorVertexExists.clear();
  549. };
  550. AZStd::shared_ptr<AzFramework::Terrain::QueryAsyncParams> asyncParams
  551. = AZStd::make_shared<AzFramework::Terrain::QueryAsyncParams>();
  552. asyncParams->m_completionCallback = completionCallback;
  553. // Only allow one thread per sector because we'll likely have multiple sectors processing at once.
  554. asyncParams->m_desiredNumberOfJobs = 1;
  555. // We can use an "EXACT" sampler here because our points are guaranteed to be aligned with terrain grid points.
  556. sector.m_jobCompletionEvent = AZStd::make_unique<AZStd::semaphore>();
  557. AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
  558. sector.m_jobContext, &AzFramework::Terrain::TerrainDataRequests::QueryRegionAsync,
  559. queryRegion,
  560. AzFramework::Terrain::TerrainDataRequests::TerrainDataMask::Heights,
  561. ProcessHeightValue,
  562. AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT,
  563. asyncParams);
  564. }
  565. void TerrainWorldDebuggerComponent::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask)
  566. {
  567. m_lastDirtyRegion = dirtyRegion;
  568. m_lastDirtyData = dataChangedMask;
  569. if ((dataChangedMask & (TerrainDataChangedMask::Settings | TerrainDataChangedMask::HeightData)) != TerrainDataChangedMask::None)
  570. {
  571. MarkDirtySectors(dirtyRegion);
  572. // Any time the world bounds potentially changes, notify that the terrain debugger's visibility bounds also changed.
  573. AzFramework::IEntityBoundsUnionRequestBus::Broadcast(
  574. &AzFramework::IEntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId());
  575. }
  576. }
  577. TerrainWorldDebuggerComponent::WireframeSector::WireframeSector(const WireframeSector& other)
  578. {
  579. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_sectorStateMutex);
  580. m_jobContext = other.m_jobContext;
  581. m_aabb = other.m_aabb;
  582. m_lineVertices = other.m_lineVertices;
  583. m_sectorVertices = other.m_sectorVertices;
  584. m_sectorVertexExists = other.m_sectorVertexExists;
  585. m_isDirty = other.m_isDirty;
  586. }
  587. TerrainWorldDebuggerComponent::WireframeSector::WireframeSector(WireframeSector&& other)
  588. {
  589. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_sectorStateMutex);
  590. m_jobContext = AZStd::move(other.m_jobContext);
  591. m_aabb = AZStd::move(other.m_aabb);
  592. m_lineVertices = AZStd::move(other.m_lineVertices);
  593. m_sectorVertices = AZStd::move(other.m_sectorVertices);
  594. m_sectorVertexExists = AZStd::move(other.m_sectorVertexExists);
  595. m_isDirty = AZStd::move(other.m_isDirty);
  596. }
  597. TerrainWorldDebuggerComponent::WireframeSector& TerrainWorldDebuggerComponent::WireframeSector::operator=(const WireframeSector& other)
  598. {
  599. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_sectorStateMutex);
  600. m_jobContext = other.m_jobContext;
  601. m_aabb = other.m_aabb;
  602. m_lineVertices = other.m_lineVertices;
  603. m_sectorVertices = other.m_sectorVertices;
  604. m_sectorVertexExists = other.m_sectorVertexExists;
  605. m_isDirty = other.m_isDirty;
  606. return *this;
  607. }
  608. TerrainWorldDebuggerComponent::WireframeSector& TerrainWorldDebuggerComponent::WireframeSector::operator=(WireframeSector&& other)
  609. {
  610. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_sectorStateMutex);
  611. m_jobContext = AZStd::move(other.m_jobContext);
  612. m_aabb = AZStd::move(other.m_aabb);
  613. m_lineVertices = AZStd::move(other.m_lineVertices);
  614. m_sectorVertices = AZStd::move(other.m_sectorVertices);
  615. m_sectorVertexExists = AZStd::move(other.m_sectorVertexExists);
  616. m_isDirty = AZStd::move(other.m_isDirty);
  617. return *this;
  618. }
  619. void TerrainWorldDebuggerComponent::WireframeSector::Reset()
  620. {
  621. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_sectorStateMutex);
  622. if (m_jobContext)
  623. {
  624. // Cancel the job and wait until it completes.
  625. m_jobContext->Cancel();
  626. m_jobCompletionEvent->acquire();
  627. m_jobCompletionEvent.reset();
  628. m_jobContext.reset();
  629. }
  630. m_aabb = AZ::Aabb::CreateNull();
  631. m_lineVertices.clear();
  632. m_sectorVertices.clear();
  633. m_sectorVertexExists.clear();
  634. m_isDirty = true;
  635. }
  636. void TerrainWorldDebuggerComponent::WireframeSector::SetDirty()
  637. {
  638. m_isDirty = true;
  639. if (m_jobContext)
  640. {
  641. m_jobContext->Cancel();
  642. }
  643. }
  644. } // namespace Terrain