diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp index 79fd5509f2..8daccd7d61 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp @@ -90,13 +90,28 @@ namespace Terrain void TerrainWorldDebuggerComponent::Activate() { - m_wireframeBounds = AZ::Aabb::CreateNull(); + // Given the AuxGeom vertex limits, MaxSectorsToDraw is the max number of wireframe sectors we can draw without exceeding the + // limits. Since we want an N x N sector grid, take the square root to get the number of sectors in each direction. + m_sectorGridSize = aznumeric_cast(sqrtf(MaxSectorsToDraw)); + + // We're always going to keep the camera in the center square, so "round" downwards to an odd number of sectors if we currently + // have an even number. (If we added a sector, we'll go above the max sectors that we can draw with our vertex limits) + m_sectorGridSize = (m_sectorGridSize & 0x01) ? m_sectorGridSize : m_sectorGridSize - 1; + + // Create our fixed set of sectors that we'll draw. By default, they'll all be constructed as dirty, so they'll get refreshed + // the first time we try to draw them. (If wireframe drawing is disabled, we'll never refresh them) + m_wireframeSectors.clear(); + m_wireframeSectors.resize(m_sectorGridSize * m_sectorGridSize); AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(GetEntityId()); AzFramework::BoundsRequestBus::Handler::BusConnect(GetEntityId()); AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect(); - RefreshCachedWireframeGrid(AZ::Aabb::CreateNull()); + // Any time the world bounds potentially changes, notify that the terrain debugger's visibility bounds also changed. + // Otherwise, DisplayEntityViewport() won't get called at the appropriate times, since the visibility could get incorrectly + // culled out. + AzFramework::IEntityBoundsUnionRequestBus::Broadcast( + &AzFramework::IEntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId()); } void TerrainWorldDebuggerComponent::Deactivate() @@ -105,7 +120,6 @@ namespace Terrain AzFramework::BoundsRequestBus::Handler::BusDisconnect(); AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect(); - m_wireframeBounds = AZ::Aabb::CreateNull(); m_wireframeSectors.clear(); } @@ -144,170 +158,239 @@ namespace Terrain return GetWorldBounds(); } - void TerrainWorldDebuggerComponent::DisplayEntityViewport( - const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) + void TerrainWorldDebuggerComponent::MarkDirtySectors(const AZ::Aabb& dirtyRegion) { - // Draw a wireframe box around the entire terrain world bounds - if (m_configuration.m_drawWorldBounds) + // 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 + // each sector. + const AZ::Aabb dirtyRegion2D = AZ::Aabb::CreateFromMinMaxValues( + dirtyRegion.GetMin().GetX(), dirtyRegion.GetMin().GetY(), AZStd::numeric_limits::lowest(), + dirtyRegion.GetMax().GetX(), dirtyRegion.GetMax().GetY(), AZStd::numeric_limits::max()); + + // For each sector that overlaps the dirty region (or all of them if the region is invalid), mark them as dirty so that + // they'll get refreshed the next time we need to draw them. + for (auto& sector : m_wireframeSectors) { - AZ::Color outlineColor(1.0f, 0.0f, 0.0f, 1.0f); - AZ::Aabb aabb = GetWorldBounds(); + if (!dirtyRegion2D.IsValid() || dirtyRegion2D.Overlaps(sector.m_aabb)) + { + sector.m_isDirty = true; + } + } + } - debugDisplay.SetColor(outlineColor); - debugDisplay.DrawWireBox(aabb.GetMin(), aabb.GetMax()); + void TerrainWorldDebuggerComponent::DrawWorldBounds(AzFramework::DebugDisplayRequests& debugDisplay) + { + if (!m_configuration.m_drawWorldBounds) + { + return; } - // Draw a wireframe representation of the terrain surface - if (m_configuration.m_drawWireframe && !m_wireframeSectors.empty()) + // Draw a wireframe box around the entire terrain world bounds + AZ::Color outlineColor(1.0f, 0.0f, 0.0f, 1.0f); + AZ::Aabb aabb = GetWorldBounds(); + + debugDisplay.SetColor(outlineColor); + debugDisplay.DrawWireBox(aabb.GetMin(), aabb.GetMax()); + } + + void TerrainWorldDebuggerComponent::DrawWireframe( + const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) + { + AZ_PROFILE_FUNCTION(Entity); + + if (!m_configuration.m_drawWireframe) { - // Start by assuming we'll draw the entire world. - AZ::Aabb drawingAabb = GetWorldBounds(); + return; + } - // Assuming we can get the camera, reduce the drawing bounds to a fixed distance around the camera. - if (auto viewportContextRequests = AZ::RPI::ViewportContextRequests::Get(); viewportContextRequests) - { - // Get the current camera position. - AZ::RPI::ViewportContextPtr viewportContext = viewportContextRequests->GetViewportContextById(viewportInfo.m_viewportId); - AZ::Vector3 cameraPos = viewportContext->GetCameraTransform().GetTranslation(); + /* This draws a wireframe centered on the camera that extends out to a certain distance at all times. To reduce the amount of + * recalculations we need to do on each camera movement, we divide the world into a conceptual grid of sectors, where each sector + * contains a fixed number of terrain height points. So for example, if the terrain has height data at 1 m spacing, the sectors + * 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 + * draws N x N sectors centered around the camera, as determined by m_sectorGridSize. So a gridSize of 7 with a sector size of + * 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 + * a new sector, we refresh the changed sectors before drawing them. + * + * 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 + * entries, so they would normally be indexed as (y * N) + x. Since we want this to be centered on the camera, the easy answer + * would be to take the camera position - (N / 2) (since we're centering) as the relative offset to the first entry. But this + * would mean that the entire set of entries would change every time we move the camera. For example, if we had 5 entries, + * they might map to 0-4, 1-5, 2-6, 3-7, etc as the camera moves. + * + * 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 + * as the camera moves. For negative entries, we rotate the indices in reverse, so that we get results like (0 1 2 3 4), + * (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 + * remained visible are left alone and don't need to be updated again. + */ + + // Get the terrain world bounds + AZ::Aabb worldBounds = GetWorldBounds(); + float worldMinZ = worldBounds.GetMin().GetZ(); - // Determine how far to draw in each direction in world space based on our MaxSectorsToDraw - AZ::Vector2 queryResolution = AZ::Vector2(1.0f); - AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); - AZ::Vector3 viewDistance( - queryResolution.GetX() * SectorSizeInGridPoints * sqrtf(MaxSectorsToDraw), - queryResolution.GetY() * SectorSizeInGridPoints * sqrtf(MaxSectorsToDraw), - 0.0f); - - // Create an AABB around the camera based on how far we want to be able to draw in each direction and clamp the - // drawing AABB to it. - AZ::Aabb cameraAabb = AZ::Aabb::CreateFromMinMax( - AZ::Vector3( - cameraPos.GetX() - viewDistance.GetX(), cameraPos.GetY() - viewDistance.GetY(), drawingAabb.GetMin().GetZ()), - AZ::Vector3( - cameraPos.GetX() + viewDistance.GetX(), cameraPos.GetY() + viewDistance.GetY(), drawingAabb.GetMin().GetZ())); - drawingAabb.Clamp(cameraAabb); - } + // Get the terrain height data resolution + AZ::Vector2 heightDataResolution = AZ::Vector2(1.0f); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + heightDataResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); + + // Get the size of a wireframe sector in world space + const AZ::Vector2 sectorSize = heightDataResolution * SectorSizeInGridPoints; - // For each sector, if it appears within our view distance, draw it. - for (auto& sector : m_wireframeSectors) + // Try to get the current camera position, or default to (0,0) if we can't. + AZ::Vector3 cameraPos = AZ::Vector3::CreateZero(); + if (auto viewportContextRequests = AZ::RPI::ViewportContextRequests::Get(); viewportContextRequests) + { + AZ::RPI::ViewportContextPtr viewportContext = viewportContextRequests->GetViewportContextById(viewportInfo.m_viewportId); + cameraPos = viewportContext->GetCameraTransform().GetTranslation(); + } + + // Convert our camera position to a wireframe grid sector. We first convert from world space to sector space by dividing by + // sectorSize, so that integer values are sectors, and fractional values are the distance within the sector. Then we get the + // floor, so that we consistently get the next lowest integer - i.e. 2.3 -> 2, and -2.3 -> -3. This gives us consistent behavior + // across both positive and negative positions. + AZ::Vector2 gridPosition = AZ::Vector2(cameraPos.GetX(), cameraPos.GetY()) / sectorSize; + int32_t cameraSectorX = aznumeric_cast(gridPosition.GetFloor().GetX()); + int32_t cameraSectorY = aznumeric_cast(gridPosition.GetFloor().GetY()); + + // Loop through each sector that we *want* to draw, based on camera position. If the current sector at that index in + // m_wireframeSectors doesn't match the world position we want, update its world position and mark it as dirty. + // (We loop from -gridSize/2 to gridSize/2 so that the camera is always in the center sector.) + for (int32_t sectorY = cameraSectorY - (m_sectorGridSize / 2); sectorY <= cameraSectorY + (m_sectorGridSize / 2); sectorY++) + { + for (int32_t sectorX = cameraSectorX - (m_sectorGridSize / 2); sectorX <= cameraSectorX + (m_sectorGridSize / 2); sectorX++) { - if (drawingAabb.Overlaps(sector.m_aabb)) + + // Calculate the index in m_wireframeSectors for this sector. Our indices should rotate through 0 - gridSize, but just + // using a single mod will produce a negative result for negative sector indices. Using abs() will give us incorrect + // "backwards" indices for negative numbers, so instead we add the grid size and mod a second time. + // Ex: For a grid size of 5, we want the indices to map like this: + // Index 0 1 2 3 4 + // Values -10 -9 -8 -7 -6 + // -5 -4 -3 -2 -1 + // 0 1 2 3 4 + // 5 6 7 8 9 + // 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. + int32_t sectorYIndex = ((sectorY % m_sectorGridSize) + m_sectorGridSize) % m_sectorGridSize; + int32_t sectorXIndex = ((sectorX % m_sectorGridSize) + m_sectorGridSize) % m_sectorGridSize; + int32_t sectorIndex = (sectorYIndex * m_sectorGridSize) + sectorXIndex; + + WireframeSector& sector = m_wireframeSectors[sectorIndex]; + + // Calculate the new world space box for this sector. + AZ::Aabb sectorAabb = AZ::Aabb::CreateFromMinMax( + AZ::Vector3(sectorX * sectorSize.GetX(), sectorY * sectorSize.GetY(), worldMinZ), + AZ::Vector3((sectorX + 1) * sectorSize.GetX(), (sectorY + 1) * sectorSize.GetY(), worldMinZ)); + + // Clamp it to the terrain world bounds. + sectorAabb.Clamp(worldBounds); + + // 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. + if (sector.m_aabb != sectorAabb) { - if (!sector.m_lineVertices.empty()) - { - const AZ::Color primaryColor = AZ::Color(0.25f, 0.25f, 0.25f, 1.0f); - debugDisplay.DrawLines(sector.m_lineVertices, primaryColor); - } - else - { - AZ_Warning("Debug", false, "empty sector!"); - } + sector.m_aabb = sectorAabb; + sector.m_isDirty = true; } } } + + // Finally, for each sector, rebuild the data if it's dirty, then draw it assuming it has valid data. + // (Sectors that are outside the world bounds won't have any valid data, so they'll get skipped) + for (auto& sector : m_wireframeSectors) + { + if (sector.m_isDirty) + { + RebuildSectorWireframe(sector, heightDataResolution, worldMinZ); + } + + if (!sector.m_lineVertices.empty()) + { + const AZ::Color primaryColor = AZ::Color(0.25f, 0.25f, 0.25f, 1.0f); + debugDisplay.DrawLines(sector.m_lineVertices, primaryColor); + } + } } - void TerrainWorldDebuggerComponent::RefreshCachedWireframeGrid(const AZ::Aabb& dirtyRegion) + + void TerrainWorldDebuggerComponent::DisplayEntityViewport( + const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) { - // Get the terrain world bounds and grid resolution. + DrawWorldBounds(debugDisplay); + DrawWireframe(viewportInfo, debugDisplay); - AZ::Aabb worldBounds = GetWorldBounds(); + } - AZ::Vector2 queryResolution = AZ::Vector2(1.0f); - AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); - - // Take the dirty region and adjust the Z values to the world min/max so that even if the dirty region falls outside the current - // world bounds, we still update the wireframe accordingly. - AZ::Aabb dirtyRegion2D = AZ::Aabb::CreateFromMinMaxValues( - dirtyRegion.GetMin().GetX(), dirtyRegion.GetMin().GetY(), worldBounds.GetMin().GetZ(), - dirtyRegion.GetMax().GetX(), dirtyRegion.GetMax().GetY(), worldBounds.GetMax().GetZ()); - - // Calculate the world size of each sector. Note that this size actually ends at the last point, not the last square. - // So for example, the sector size for 3 points will go from (*--*--*) even though it will be used to draw (*--*--*--). - const float xSectorSize = (queryResolution.GetX() * SectorSizeInGridPoints); - const float ySectorSize = (queryResolution.GetY() * SectorSizeInGridPoints); - - // Calculate the total number of sectors to cache. The world bounds might not be evenly divisible by sector bounds, so we add - // an extra sector's worth of size in each direction so that clamping down to an integer still accounts for that fractional sector. - const int32_t numSectorsX = aznumeric_cast((worldBounds.GetXExtent() + xSectorSize) / xSectorSize); - const int32_t numSectorsY = aznumeric_cast((worldBounds.GetYExtent() + ySectorSize) / ySectorSize); - - // If we haven't cached anything before, or if the world bounds has changed, clear our cache structure and repopulate it - // with WireframeSector entries with the proper AABB sizes. - if (!m_wireframeBounds.IsValid() || !dirtyRegion2D.IsValid() || !m_wireframeBounds.IsClose(worldBounds)) + void TerrainWorldDebuggerComponent::RebuildSectorWireframe(WireframeSector& sector, const AZ::Vector2& gridResolution, float worldMinZ) + { + if (!sector.m_isDirty) { - m_wireframeBounds = worldBounds; + return; + } - m_wireframeSectors.clear(); - m_wireframeSectors.reserve(numSectorsX * numSectorsY); + sector.m_isDirty = false; - for (int32_t ySector = 0; ySector < numSectorsY; ySector++) - { - for (int32_t xSector = 0; xSector < numSectorsX; xSector++) - { - // For each sector, set up the AABB for the sector and reserve memory for the line vertices. - WireframeSector sector; - sector.m_lineVertices.reserve(VerticesPerSector); - sector.m_aabb = AZ::Aabb::CreateFromMinMax( - AZ::Vector3( - worldBounds.GetMin().GetX() + (xSector * xSectorSize), worldBounds.GetMin().GetY() + (ySector * ySectorSize), - worldBounds.GetMin().GetZ()), - AZ::Vector3( - worldBounds.GetMin().GetX() + ((xSector + 1) * xSectorSize), - worldBounds.GetMin().GetY() + ((ySector + 1) * ySectorSize), worldBounds.GetMax().GetZ())); - - sector.m_aabb.Clamp(worldBounds); - - m_wireframeSectors.push_back(AZStd::move(sector)); - } - } + // To rebuild the wireframe, we walk through the sector by X, then by Y. For each point, we add two lines in a _| shape. + // To do that, we'll need to cache the height from the previous point to draw the _ line, and from the previous row to draw + // the | line. - // Notify the visibility system that our bounds have changed. - AzFramework::IEntityBoundsUnionRequestBus::Broadcast( - &AzFramework::IEntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId()); - } + // When walking through the bounding box, the loops will be inclusive on one side, and exclusive on the other. However, since + // our box is exactly aligned with grid points, we want to get the grid points on both sides in each direction, so we need to + // expand our query region by one extra point. + // For example, if our AABB is 2 m and our grid resolution is 1 m, we'll want to query (*--*--*--), not (*--*--). + // Since we're processing lines based on the grid points and going backwards, this will give us (*--*--*). - // For each sector, if it overlaps with the dirty region, clear it out and recache the wireframe line data. - for (auto& sector : m_wireframeSectors) + AZ::Aabb region = sector.m_aabb; + region.SetMax(region.GetMax() + AZ::Vector3(gridResolution.GetX(), gridResolution.GetY(), 0.0f)); + + // This keeps track of the height from the previous point for the _ line. + float previousHeight = 0.0f; + + // This keeps track of the heights from the previous row for the | line. + AZStd::vector rowHeights(aznumeric_cast(ceil(region.GetExtents().GetX() / gridResolution.GetX()))); + + // We need 4 vertices for each grid point in our sector to hold the _| shape. + const uint32_t numSamplesX = static_cast((region.GetMax().GetX() - region.GetMin().GetX()) / gridResolution.GetX()); + const uint32_t numSamplesY = static_cast((region.GetMax().GetY() - region.GetMin().GetY()) / gridResolution.GetY()); + sector.m_lineVertices.clear(); + sector.m_lineVertices.reserve(numSamplesX * numSamplesY * 4); + + // For each terrain height value in the region, create the _| grid lines for that point and cache off the height value + // for use with subsequent grid line calculations. + auto ProcessHeightValue = [gridResolution, &previousHeight, &rowHeights, §or] + (uint32_t xIndex, uint32_t yIndex, const AZ::Vector3& position, [[maybe_unused]] bool terrainExists) { - if (dirtyRegion2D.IsValid() && !dirtyRegion2D.Overlaps(sector.m_aabb)) + // Don't add any vertices for the first column or first row. These grid lines will be handled by an adjacent sector, if + // there is one. + if ((xIndex > 0) && (yIndex > 0)) { - continue; + float x = position.GetX() - gridResolution.GetX(); + float y = position.GetY() - gridResolution.GetY(); + + sector.m_lineVertices.emplace_back(AZ::Vector3(x, position.GetY(), previousHeight)); + sector.m_lineVertices.emplace_back(position); + + sector.m_lineVertices.emplace_back(AZ::Vector3(position.GetX(), y, rowHeights[xIndex])); + sector.m_lineVertices.emplace_back(position); } - sector.m_lineVertices.clear(); + // Save off the heights so that we can use them to draw subsequent columns and rows. + previousHeight = position.GetZ(); + rowHeights[xIndex] = position.GetZ(); + }; - for (float y = sector.m_aabb.GetMin().GetY(); y < sector.m_aabb.GetMax().GetY(); y += queryResolution.GetY()) + // This set of nested loops will get replaced with a call to ProcessHeightsFromRegion once the API exists. + uint32_t yIndex = 0; + for (float y = region.GetMin().GetY(); y < region.GetMax().GetY(); y += gridResolution.GetY()) + { + uint32_t xIndex = 0; + for (float x = region.GetMin().GetX(); x < region.GetMax().GetX(); x += gridResolution.GetX()) { - for (float x = sector.m_aabb.GetMin().GetX(); x < sector.m_aabb.GetMax().GetX(); x += queryResolution.GetX()) - { - float x1 = x + queryResolution.GetX(); - float y1 = y + queryResolution.GetY(); - - float z00 = 0.0f; - float z01 = 0.0f; - float z10 = 0.0f; - bool terrainExists; - - AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - z00, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y, - AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); - AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - z01, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y1, - AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); - AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - z10, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x1, y, - AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); - - sector.m_lineVertices.push_back(AZ::Vector3(x, y, z00)); - sector.m_lineVertices.push_back(AZ::Vector3(x1, y, z10)); - - sector.m_lineVertices.push_back(AZ::Vector3(x, y, z00)); - sector.m_lineVertices.push_back(AZ::Vector3(x, y1, z01)); - } + float height = worldMinZ; + bool terrainExists = false; + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + height, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y, + AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); + ProcessHeightValue(xIndex, yIndex, AZ::Vector3(x, y, height), terrainExists); + xIndex++; } + yIndex++; } } @@ -315,7 +398,14 @@ namespace Terrain { if (dataChangedMask & (TerrainDataChangedMask::Settings | TerrainDataChangedMask::HeightData)) { - RefreshCachedWireframeGrid(dirtyRegion); + MarkDirtySectors(dirtyRegion); + } + + if (dataChangedMask & TerrainDataChangedMask::Settings) + { + // Any time the world bounds potentially changes, notify that the terrain debugger's visibility bounds also changed. + AzFramework::IEntityBoundsUnionRequestBus::Broadcast( + &AzFramework::IEntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId()); } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.h b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.h index 5ec831c038..f3bcead8c3 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.h @@ -82,33 +82,40 @@ namespace Terrain private: + TerrainWorldDebuggerConfig m_configuration; + // Cache our debug wireframe representation in "sectors" of data so that we can easily control how far out we draw // the wireframe representation in each direction. struct WireframeSector { - AZ::Aabb m_aabb; + AZ::Aabb m_aabb{ AZ::Aabb::CreateNull() }; AZStd::vector m_lineVertices; + bool m_isDirty{ true }; }; + void RebuildSectorWireframe(WireframeSector& sector, const AZ::Vector2& gridResolution, float worldMinZ); + void MarkDirtySectors(const AZ::Aabb& dirtyRegion); + void DrawWorldBounds(AzFramework::DebugDisplayRequests& debugDisplay); + void DrawWireframe(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay); + // Each sector contains an N x N grid of squares that it will draw. Since this is a count of the number of terrain grid points // in each direction, the actual world size will depend on the terrain grid resolution in each direction. static constexpr int32_t SectorSizeInGridPoints = 10; - // For each grid point we will draw half a square (left-right, top-down), so we need 4 vertices for the two lines. + // For each grid point we will draw half a square ( _| ), so we need 4 vertices for the two lines. static constexpr int32_t VerticesPerGridPoint = 4; - // Pre-calculate the total number of vertices per sector. - static constexpr int32_t VerticesPerSector = - (SectorSizeInGridPoints * VerticesPerGridPoint) * (SectorSizeInGridPoints * VerticesPerGridPoint); + // Pre-calculate the total number of vertices per sector (N x N grid points, with 4 vertices per grid point) + static constexpr int32_t VerticesPerSector = (SectorSizeInGridPoints * SectorSizeInGridPoints) * VerticesPerGridPoint; // AuxGeom has limits to the number of lines it can draw in a frame, so we'll cap how many total sectors to draw. static constexpr int32_t MaxVerticesToDraw = 500000; static constexpr int32_t MaxSectorsToDraw = MaxVerticesToDraw / VerticesPerSector; - void RefreshCachedWireframeGrid(const AZ::Aabb& dirtyRegion); - - TerrainWorldDebuggerConfig m_configuration; + // Structure to keep track of all our current wireframe sectors, so that we don't have to recalculate them every frame. AZStd::vector m_wireframeSectors; - AZ::Aabb m_wireframeBounds; + + // The size in sectors of our wireframe grid in each direction (i.e. a 5 x 5 sector grid has a sectorGridSize of 5) + int32_t m_sectorGridSize{ 0 }; }; }