Optimized Terrain Debugger Wireframe rendering (#6572)

* Optimized wireframe drawing.
As a part of rearranging the code to make use of the upcoming ProcessHeightsFromRegion, the number of calls to GetHeight could be reduced, dropping the refresh time in my test case from 2550 ms to 1068 ms.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Change refresh to only happen on wireframe draws.
This helps ensure that multiple data changes in a single frame don't cause multiple refreshes, and prevents us from taking a refresh penalty when the wireframe isn't being drawn at all.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Change debugger to update wireframes incrementally.
This works by having a fixed NxN grid of sectors.  The camera is always considered as being in the center of the grid, so anytime the camera's grid square changes, only a subset of sectors are updated to have the new wireframe data.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Bugfix - sector vertex count was 4x too high.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Bugfixes & comments.
Fixed initial "mark dirty" refresh - the dirty region Z value needed to be ignored.
Added copious comments for the math & logic, and simplified some of the math.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Small update to comment for better readability.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Addressed PR feedback.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>
monroegm-disable-blank-issue-2
Mike Balfour 4 years ago committed by GitHub
parent ad38c788f9
commit 49dd17f410
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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<int32_t>(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<float>::lowest(),
dirtyRegion.GetMax().GetX(), dirtyRegion.GetMax().GetY(), AZStd::numeric_limits<float>::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<int32_t>(gridPosition.GetFloor().GetX());
int32_t cameraSectorY = aznumeric_cast<int32_t>(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<int32_t>((worldBounds.GetXExtent() + xSectorSize) / xSectorSize);
const int32_t numSectorsY = aznumeric_cast<int32_t>((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<float> rowHeights(aznumeric_cast<size_t>(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<uint32_t>((region.GetMax().GetX() - region.GetMin().GetX()) / gridResolution.GetX());
const uint32_t numSamplesY = static_cast<uint32_t>((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, &sector]
(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());
}
}

@ -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<AZ::Vector3> 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<WireframeSector> 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 };
};
}

Loading…
Cancel
Save