Merge pull request #6974 from aws-lumberyard-dev/SJ/TerrainOptimization
[Terrain] Update Terrain components to use the new ProcessRegion API functions
This commit is contained in:
@@ -161,6 +161,11 @@ namespace AzFramework
|
||||
SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT) const = 0;
|
||||
|
||||
//! Returns the number of samples for a given region and step size. The first and second
|
||||
//! elements of the pair correspond to the X and Y sample counts respectively.
|
||||
virtual AZStd::pair<size_t, size_t> GetNumSamplesFromRegion(const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize) const = 0;
|
||||
|
||||
//! Given a region(aabb) and a step size, call the provided callback function with surface data corresponding to the
|
||||
//! coordinates in the region.
|
||||
virtual void ProcessHeightsFromRegion(const AZ::Aabb& inRegion,
|
||||
|
||||
@@ -92,6 +92,8 @@ namespace UnitTest
|
||||
ProcessSurfaceWeightsFromListOfVector2, void(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
|
||||
MOCK_CONST_METHOD3(
|
||||
ProcessSurfacePointsFromListOfVector2, void(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
|
||||
MOCK_CONST_METHOD2(
|
||||
GetNumSamplesFromRegion, AZStd::pair<size_t, size_t>(const AZ::Aabb&, const AZ::Vector2&));
|
||||
MOCK_CONST_METHOD4(
|
||||
ProcessHeightsFromRegion, void(const AZ::Aabb&, const AZ::Vector2&, AzFramework::Terrain::SurfacePointRegionFillCallback, Sampler));
|
||||
MOCK_CONST_METHOD4(
|
||||
|
||||
@@ -284,21 +284,14 @@ namespace Terrain
|
||||
heights.clear();
|
||||
heights.reserve(gridWidth * gridHeight);
|
||||
|
||||
for (int32_t row = 0; row < gridHeight; row++)
|
||||
auto perPositionHeightCallback = [&heights, worldCenterZ]
|
||||
([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex, const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
const float y = row * gridResolution.GetY() + worldSize.GetMin().GetY();
|
||||
for (int32_t col = 0; col < gridWidth; col++)
|
||||
{
|
||||
const float x = col * gridResolution.GetX() + worldSize.GetMin().GetX();
|
||||
float height = 0.0f;
|
||||
heights.emplace_back(surfacePoint.m_position.GetZ() - worldCenterZ);
|
||||
};
|
||||
|
||||
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
|
||||
height, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, nullptr);
|
||||
|
||||
heights.emplace_back(height - worldCenterZ);
|
||||
}
|
||||
}
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromRegion,
|
||||
worldSize, gridResolution, perPositionHeightCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT);
|
||||
}
|
||||
|
||||
uint8_t TerrainPhysicsColliderComponent::GetMaterialIdIndex(const Physics::MaterialId& materialId, const AZStd::vector<Physics::MaterialId>& materialList) const
|
||||
@@ -350,42 +343,37 @@ namespace Terrain
|
||||
|
||||
AZStd::vector<Physics::MaterialId> materialList = GetMaterialList();
|
||||
|
||||
for (int32_t row = 0; row < gridHeight; row++)
|
||||
auto perPositionCallback = [&heightMaterials, &materialList, this, worldCenterZ, worldHeightBoundsMin, worldHeightBoundsMax]
|
||||
([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex, const AzFramework::SurfaceData::SurfacePoint& surfacePoint, bool terrainExists)
|
||||
{
|
||||
const float y = row * gridResolution.GetY() + worldSize.GetMin().GetY();
|
||||
for (int32_t col = 0; col < gridWidth; col++)
|
||||
float height = surfacePoint.m_position.GetZ();
|
||||
|
||||
// Any heights that fall outside the range of our bounding box will get turned into holes.
|
||||
if ((height < worldHeightBoundsMin) || (height > worldHeightBoundsMax))
|
||||
{
|
||||
const float x = col * gridResolution.GetX() + worldSize.GetMin().GetX();
|
||||
float height = 0.0f;
|
||||
|
||||
bool terrainExists = true;
|
||||
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
|
||||
height, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, &terrainExists);
|
||||
|
||||
// Any heights that fall outside the range of our bounding box will get turned into holes.
|
||||
if ((height < worldHeightBoundsMin) || (height > worldHeightBoundsMax))
|
||||
{
|
||||
height = worldHeightBoundsMin;
|
||||
terrainExists = false;
|
||||
}
|
||||
|
||||
// Find the best surface tag at this point.
|
||||
AzFramework::SurfaceData::SurfaceTagWeight surfaceWeight;
|
||||
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
|
||||
surfaceWeight, &AzFramework::Terrain::TerrainDataRequests::GetMaxSurfaceWeightFromFloats, x, y,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, nullptr);
|
||||
|
||||
Physics::HeightMaterialPoint point;
|
||||
point.m_height = height - worldCenterZ;
|
||||
point.m_quadMeshType = terrainExists ? Physics::QuadMeshType::SubdivideUpperLeftToBottomRight : Physics::QuadMeshType::Hole;
|
||||
|
||||
Physics::MaterialId materialId = FindMaterialIdForSurfaceTag(surfaceWeight.m_surfaceType);
|
||||
point.m_materialIndex = GetMaterialIdIndex(materialId, materialList);
|
||||
|
||||
heightMaterials.emplace_back(point);
|
||||
height = worldHeightBoundsMin;
|
||||
terrainExists = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the best surface tag at this point.
|
||||
// We want the MaxSurfaceWeight. The ProcessSurfacePoints callback has surface weights sorted.
|
||||
// So, we pick the value at the front of the list.
|
||||
AzFramework::SurfaceData::SurfaceTagWeight surfaceWeight;
|
||||
if (!surfacePoint.m_surfaceTags.empty())
|
||||
{
|
||||
surfaceWeight = *surfacePoint.m_surfaceTags.begin();
|
||||
}
|
||||
|
||||
Physics::HeightMaterialPoint point;
|
||||
point.m_height = height - worldCenterZ;
|
||||
point.m_quadMeshType = terrainExists ? Physics::QuadMeshType::SubdivideUpperLeftToBottomRight : Physics::QuadMeshType::Hole;
|
||||
Physics::MaterialId materialId = FindMaterialIdForSurfaceTag(surfaceWeight.m_surfaceType);
|
||||
point.m_materialIndex = GetMaterialIdIndex(materialId, materialList);
|
||||
heightMaterials.emplace_back(point);
|
||||
};
|
||||
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::ProcessSurfacePointsFromRegion,
|
||||
worldSize, gridResolution, perPositionCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT);
|
||||
}
|
||||
|
||||
AZ::Vector2 TerrainPhysicsColliderComponent::GetHeightfieldGridSpacing() const
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace Terrain
|
||||
{
|
||||
if (sector.m_isDirty)
|
||||
{
|
||||
RebuildSectorWireframe(sector, heightDataResolution, worldMinZ);
|
||||
RebuildSectorWireframe(sector, heightDataResolution);
|
||||
}
|
||||
|
||||
if (!sector.m_lineVertices.empty())
|
||||
@@ -317,7 +317,7 @@ namespace Terrain
|
||||
|
||||
}
|
||||
|
||||
void TerrainWorldDebuggerComponent::RebuildSectorWireframe(WireframeSector& sector, const AZ::Vector2& gridResolution, float worldMinZ)
|
||||
void TerrainWorldDebuggerComponent::RebuildSectorWireframe(WireframeSector& sector, const AZ::Vector2& gridResolution)
|
||||
{
|
||||
if (!sector.m_isDirty)
|
||||
{
|
||||
@@ -354,44 +354,29 @@ namespace Terrain
|
||||
// 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)
|
||||
(size_t xIndex, size_t yIndex, const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
// 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))
|
||||
{
|
||||
float x = position.GetX() - gridResolution.GetX();
|
||||
float y = position.GetY() - gridResolution.GetY();
|
||||
float x = surfacePoint.m_position.GetX() - gridResolution.GetX();
|
||||
float y = surfacePoint.m_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(x, surfacePoint.m_position.GetY(), previousHeight));
|
||||
sector.m_lineVertices.emplace_back(surfacePoint.m_position);
|
||||
|
||||
sector.m_lineVertices.emplace_back(AZ::Vector3(position.GetX(), y, rowHeights[xIndex]));
|
||||
sector.m_lineVertices.emplace_back(position);
|
||||
sector.m_lineVertices.emplace_back(AZ::Vector3(surfacePoint.m_position.GetX(), y, rowHeights[xIndex]));
|
||||
sector.m_lineVertices.emplace_back(surfacePoint.m_position);
|
||||
}
|
||||
|
||||
// Save off the heights so that we can use them to draw subsequent columns and rows.
|
||||
previousHeight = position.GetZ();
|
||||
rowHeights[xIndex] = position.GetZ();
|
||||
previousHeight = surfacePoint.m_position.GetZ();
|
||||
rowHeights[xIndex] = surfacePoint.m_position.GetZ();
|
||||
};
|
||||
|
||||
// This set of nested loops will get replaced with a call to ProcessHeightsFromRegion once the API exists.
|
||||
for (size_t yIndex = 0; yIndex < numSamplesY; yIndex++)
|
||||
{
|
||||
float y = region.GetMin().GetY() + (gridResolution.GetY() * yIndex);
|
||||
for (size_t xIndex = 0; xIndex < numSamplesX; xIndex++)
|
||||
{
|
||||
float x = region.GetMin().GetX() + (gridResolution.GetX() * xIndex);
|
||||
|
||||
float height = worldMinZ;
|
||||
bool terrainExists = false;
|
||||
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
|
||||
height, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists);
|
||||
ProcessHeightValue(
|
||||
aznumeric_cast<uint32_t>(xIndex), aznumeric_cast<uint32_t>(yIndex), AZ::Vector3(x, y, height), terrainExists);
|
||||
}
|
||||
}
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromRegion,
|
||||
region, gridResolution, ProcessHeightValue, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT);
|
||||
}
|
||||
|
||||
void TerrainWorldDebuggerComponent::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask)
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Terrain
|
||||
bool m_isDirty{ true };
|
||||
};
|
||||
|
||||
void RebuildSectorWireframe(WireframeSector& sector, const AZ::Vector2& gridResolution, float worldMinZ);
|
||||
void RebuildSectorWireframe(WireframeSector& sector, const AZ::Vector2& gridResolution);
|
||||
void MarkDirtySectors(const AZ::Aabb& dirtyRegion);
|
||||
void DrawWorldBounds(AzFramework::DebugDisplayRequests& debugDisplay);
|
||||
void DrawWireframe(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay);
|
||||
|
||||
@@ -751,50 +751,56 @@ namespace Terrain
|
||||
pixels.resize((quadrantWorldArea.m_max.m_x - quadrantWorldArea.m_min.m_x) * (quadrantWorldArea.m_max.m_y - quadrantWorldArea.m_min.m_y));
|
||||
uint32_t index = 0;
|
||||
|
||||
for (int yPos = quadrantWorldArea.m_min.m_y; yPos < quadrantWorldArea.m_max.m_y; ++yPos)
|
||||
auto perPositionCallback = [this, &pixels, &index](
|
||||
[[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex,
|
||||
const AzFramework::SurfaceData::SurfacePoint& surfacePoint,
|
||||
[[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
for (int xPos = quadrantWorldArea.m_min.m_x; xPos < quadrantWorldArea.m_max.m_x; ++xPos)
|
||||
// Store the top two surface weights in the texture with m_blend storing the relative weight.
|
||||
bool isFirstMaterial = true;
|
||||
float firstWeight = 0.0f;
|
||||
AZ::Vector2 position(surfacePoint.m_position.GetX(), surfacePoint.m_position.GetY());
|
||||
for (const auto& surfaceTagWeight : surfacePoint.m_surfaceTags)
|
||||
{
|
||||
AZ::Vector2 position = AZ::Vector2(xPos * DetailTextureScale, yPos * DetailTextureScale);
|
||||
AzFramework::SurfaceData::SurfaceTagWeightList surfaceWeights;
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::GetSurfaceWeightsFromVector2, position, surfaceWeights, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, nullptr);
|
||||
|
||||
// Store the top two surface weights in the texture with m_blend storing the relative weight.
|
||||
bool isFirstMaterial = true;
|
||||
float firstWeight = 0.0f;
|
||||
for (const auto& surfaceTagWeight : surfaceWeights)
|
||||
if (surfaceTagWeight.m_weight > 0.0f)
|
||||
{
|
||||
if (surfaceTagWeight.m_weight > 0.0f)
|
||||
AZ::Crc32 surfaceType = surfaceTagWeight.m_surfaceType;
|
||||
uint16_t materialId = GetDetailMaterialForSurfaceTypeAndPosition(surfaceType, position);
|
||||
if (materialId != m_detailMaterials.NoFreeSlot && materialId < 255)
|
||||
{
|
||||
AZ::Crc32 surfaceType = surfaceTagWeight.m_surfaceType;
|
||||
uint16_t materialId = GetDetailMaterialForSurfaceTypeAndPosition(surfaceType, position);
|
||||
if (materialId != m_detailMaterials.NoFreeSlot && materialId < 255)
|
||||
if (isFirstMaterial)
|
||||
{
|
||||
if (isFirstMaterial)
|
||||
{
|
||||
pixels.at(index).m_material1 = aznumeric_cast<uint8_t>(materialId);
|
||||
firstWeight = surfaceTagWeight.m_weight;
|
||||
// m_blend only needs to be calculated is material 2 is found, otherwise the initial value of 0 is correct.
|
||||
isFirstMaterial = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
pixels.at(index).m_material2 = aznumeric_cast<uint8_t>(materialId);
|
||||
float totalWeight = firstWeight + surfaceTagWeight.m_weight;
|
||||
float blendWeight = 1.0f - (firstWeight / totalWeight);
|
||||
pixels.at(index).m_blend = aznumeric_cast<uint8_t>(AZStd::round(blendWeight * 255.0f));
|
||||
break;
|
||||
}
|
||||
pixels.at(index).m_material1 = aznumeric_cast<uint8_t>(materialId);
|
||||
firstWeight = surfaceTagWeight.m_weight;
|
||||
// m_blend only needs to be calculated is material 2 is found, otherwise the initial value of 0 is correct.
|
||||
isFirstMaterial = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
pixels.at(index).m_material2 = aznumeric_cast<uint8_t>(materialId);
|
||||
float totalWeight = firstWeight + surfaceTagWeight.m_weight;
|
||||
float blendWeight = 1.0f - (firstWeight / totalWeight);
|
||||
pixels.at(index).m_blend = aznumeric_cast<uint8_t>(AZStd::round(blendWeight * 255.0f));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break; // since the list is ordered, no other materials are in the list with positive weights.
|
||||
}
|
||||
}
|
||||
++index;
|
||||
else
|
||||
{
|
||||
break; // since the list is ordered, no other materials are in the list with positive weights.
|
||||
}
|
||||
}
|
||||
}
|
||||
++index;
|
||||
};
|
||||
|
||||
AZ::Vector3 worldMin(quadrantWorldArea.m_min.m_x * DetailTextureScale, quadrantWorldArea.m_min.m_y * DetailTextureScale, 0.0f);
|
||||
AZ::Vector3 worldMax(quadrantWorldArea.m_max.m_x * DetailTextureScale, quadrantWorldArea.m_max.m_y * DetailTextureScale, 0.0f);
|
||||
AZ::Vector2 stepSize(DetailTextureScale);
|
||||
AZ::Aabb region;
|
||||
region.Set(worldMin, worldMax);
|
||||
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::ProcessSurfaceWeightsFromRegion,
|
||||
region, stepSize, perPositionCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT);
|
||||
|
||||
const int32_t left = quadrantTextureArea.m_min.m_x;
|
||||
const int32_t top = quadrantTextureArea.m_min.m_y;
|
||||
|
||||
@@ -208,12 +208,21 @@ namespace Terrain
|
||||
}
|
||||
|
||||
int32_t xStart = aznumeric_cast<int32_t>(AZStd::ceilf(m_dirtyRegion.GetMin().GetX() / m_sampleSpacing));
|
||||
int32_t xEnd = aznumeric_cast<int32_t>(AZStd::floorf(m_dirtyRegion.GetMax().GetX() / m_sampleSpacing)) + 1;
|
||||
int32_t yStart = aznumeric_cast<int32_t>(AZStd::ceilf(m_dirtyRegion.GetMin().GetY() / m_sampleSpacing));
|
||||
int32_t yEnd = aznumeric_cast<int32_t>(AZStd::floorf(m_dirtyRegion.GetMax().GetY() / m_sampleSpacing)) + 1;
|
||||
uint32_t updateWidth = xEnd - xStart;
|
||||
uint32_t updateHeight = yEnd - yStart;
|
||||
|
||||
AZ::Vector2 stepSize(m_sampleSpacing);
|
||||
AZ::Vector3 maxBound(
|
||||
m_dirtyRegion.GetMax().GetX() + m_sampleSpacing, m_dirtyRegion.GetMax().GetY() + m_sampleSpacing, 0.0f);
|
||||
AZ::Aabb region;
|
||||
region.Set(m_dirtyRegion.GetMin(), maxBound);
|
||||
|
||||
AZStd::pair<size_t, size_t> numSamples;
|
||||
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
|
||||
numSamples, &AzFramework::Terrain::TerrainDataRequests::GetNumSamplesFromRegion,
|
||||
region, stepSize);
|
||||
|
||||
uint32_t updateWidth = static_cast<uint32_t>(numSamples.first);
|
||||
uint32_t updateHeight = static_cast<uint32_t>(numSamples.second);
|
||||
AZStd::vector<uint16_t> pixels;
|
||||
pixels.reserve(updateWidth * updateHeight);
|
||||
{
|
||||
@@ -226,25 +235,21 @@ namespace Terrain
|
||||
auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false);
|
||||
typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex);
|
||||
|
||||
for (int32_t y = yStart; y < yEnd; y++)
|
||||
auto perPositionCallback = [this, &pixels]
|
||||
([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex,
|
||||
const AzFramework::SurfaceData::SurfacePoint& surfacePoint,
|
||||
[[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
for (int32_t x = xStart; x < xEnd; x++)
|
||||
{
|
||||
bool terrainExists = true;
|
||||
float terrainHeight = 0.0f;
|
||||
float xPos = x * m_sampleSpacing;
|
||||
float yPos = y * m_sampleSpacing;
|
||||
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
|
||||
terrainHeight, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats,
|
||||
xPos, yPos, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists);
|
||||
const float clampedHeight = AZ::GetClamp((surfacePoint.m_position.GetZ() - m_terrainBounds.GetMin().GetZ()) / m_terrainBounds.GetExtents().GetZ(), 0.0f, 1.0f);
|
||||
const float expandedHeight = AZStd::roundf(clampedHeight * AZStd::numeric_limits<uint16_t>::max());
|
||||
const uint16_t uint16Height = aznumeric_cast<uint16_t>(expandedHeight);
|
||||
|
||||
const float clampedHeight = AZ::GetClamp((terrainHeight - m_terrainBounds.GetMin().GetZ()) / m_terrainBounds.GetExtents().GetZ(), 0.0f, 1.0f);
|
||||
const float expandedHeight = AZStd::roundf(clampedHeight * AZStd::numeric_limits<uint16_t>::max());
|
||||
const uint16_t uint16Height = aznumeric_cast<uint16_t>(expandedHeight);
|
||||
pixels.push_back(uint16Height);
|
||||
};
|
||||
|
||||
pixels.push_back(uint16Height);
|
||||
}
|
||||
}
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(
|
||||
&AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromRegion,
|
||||
region, stepSize, perPositionCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT);
|
||||
}
|
||||
|
||||
if (m_heightmapImage)
|
||||
|
||||
@@ -690,6 +690,16 @@ void TerrainSystem::ProcessSurfacePointsFromListOfVector2(
|
||||
}
|
||||
}
|
||||
|
||||
AZStd::pair<size_t, size_t> TerrainSystem::GetNumSamplesFromRegion(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize) const
|
||||
{
|
||||
const size_t numSamplesX = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetX() / stepSize.GetX()));
|
||||
const size_t numSamplesY = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetY() / stepSize.GetY()));
|
||||
|
||||
return AZStd::make_pair(numSamplesX, numSamplesY);
|
||||
}
|
||||
|
||||
void TerrainSystem::ProcessHeightsFromRegion(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
|
||||
@@ -163,6 +163,11 @@ namespace Terrain
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT) const override;
|
||||
|
||||
//! Returns the number of samples for a given region and step size. The first and second
|
||||
//! elements of the pair correspond to the X and Y sample counts respectively.
|
||||
virtual AZStd::pair<size_t, size_t> GetNumSamplesFromRegion(const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize) const override;
|
||||
|
||||
//! Given a region(aabb) and a step size, call the provided callback function with surface data corresponding to the
|
||||
//! coordinates in the region.
|
||||
virtual void ProcessHeightsFromRegion(const AZ::Aabb& inRegion,
|
||||
|
||||
@@ -69,6 +69,45 @@ protected:
|
||||
m_colliderComponent = m_entity->CreateComponent<Terrain::TerrainPhysicsColliderComponent>(Terrain::TerrainPhysicsColliderConfig());
|
||||
m_app.RegisterComponentDescriptor(m_colliderComponent->CreateDescriptor());
|
||||
}
|
||||
|
||||
void ProcessRegionLoop(const AZ::Aabb& inRegion, const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
AzFramework::SurfaceData::SurfaceTagWeightList* surfaceTags,
|
||||
float mockHeight)
|
||||
{
|
||||
if (!perPositionCallback)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t numSamplesX = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetX() / stepSize.GetX()));
|
||||
const size_t numSamplesY = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetY() / stepSize.GetY()));
|
||||
|
||||
AzFramework::SurfaceData::SurfacePoint surfacePoint;
|
||||
for (size_t y = 0; y < numSamplesY; y++)
|
||||
{
|
||||
float fy = aznumeric_cast<float>(inRegion.GetMin().GetY() + (y * stepSize.GetY()));
|
||||
for (size_t x = 0; x < numSamplesX; x++)
|
||||
{
|
||||
bool terrainExists = false;
|
||||
float fx = aznumeric_cast<float>(inRegion.GetMin().GetX() + (x * stepSize.GetX()));
|
||||
surfacePoint.m_position.Set(fx, fy, mockHeight);
|
||||
if (surfaceTags)
|
||||
{
|
||||
surfacePoint.m_surfaceTags.clear();
|
||||
if (fy < 128.0)
|
||||
{
|
||||
surfacePoint.m_surfaceTags.push_back(surfaceTags->at(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
surfacePoint.m_surfaceTags.push_back(surfaceTags->at(1));
|
||||
}
|
||||
}
|
||||
perPositionCallback(x, y, surfacePoint, terrainExists);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(TerrainPhysicsColliderComponentTest, ActivateEntityActivateSuccess)
|
||||
@@ -238,6 +277,14 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderGetHeightsRetu
|
||||
AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f);
|
||||
NiceMock<UnitTest::MockTerrainDataRequests> terrainListener;
|
||||
ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution));
|
||||
ON_CALL(terrainListener, ProcessHeightsFromRegion).WillByDefault(
|
||||
[this](const AZ::Aabb& inRegion, const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
[[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter)
|
||||
{
|
||||
ProcessRegionLoop(inRegion, stepSize, perPositionCallback, nullptr, 0.0f);
|
||||
}
|
||||
);
|
||||
|
||||
int32_t cols, rows;
|
||||
Physics::HeightfieldProviderRequestsBus::Event(
|
||||
@@ -271,8 +318,15 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderReturnsRelativ
|
||||
AZ::Vector2 mockHeightResolution = AZ::Vector2(1.0f);
|
||||
|
||||
NiceMock<UnitTest::MockTerrainDataRequests> terrainListener;
|
||||
ON_CALL(terrainListener, GetHeightFromFloats).WillByDefault(Return(mockHeight));
|
||||
ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution));
|
||||
ON_CALL(terrainListener, ProcessHeightsFromRegion).WillByDefault(
|
||||
[this, mockHeight](const AZ::Aabb& inRegion, const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
[[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter)
|
||||
{
|
||||
ProcessRegionLoop(inRegion, stepSize, perPositionCallback, nullptr, mockHeight);
|
||||
}
|
||||
);
|
||||
|
||||
// Just return the bounds as setup. This is equivalent to the box being at the origin.
|
||||
NiceMock<UnitTest::MockShapeComponentRequests> boxShape(m_entity->GetId());
|
||||
@@ -414,22 +468,18 @@ TEST_F(TerrainPhysicsColliderComponentTest, TerrainPhysicsColliderGetHeightsAndM
|
||||
return2.m_surfaceType = tag2;
|
||||
return2.m_weight = 1.0f;
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeightList surfaceTags = { return1, return2 };
|
||||
|
||||
NiceMock<UnitTest::MockTerrainDataRequests> terrainListener;
|
||||
ON_CALL(terrainListener, GetTerrainHeightQueryResolution).WillByDefault(Return(mockHeightResolution));
|
||||
ON_CALL(terrainListener, GetHeightFromFloats).WillByDefault(Return(mockHeight));
|
||||
ON_CALL(terrainListener, GetMaxSurfaceWeightFromFloats)
|
||||
.WillByDefault(
|
||||
[return1, return2](
|
||||
[[maybe_unused]] float x, [[maybe_unused]] float y,
|
||||
[[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter, [[maybe_unused]] bool* terrainExistsPtr)
|
||||
{
|
||||
// return tag1 for the first half of the rows, tag2 for the rest.
|
||||
if (y < 128.0)
|
||||
{
|
||||
return return1;
|
||||
}
|
||||
return return2;
|
||||
});
|
||||
ON_CALL(terrainListener, ProcessSurfacePointsFromRegion).WillByDefault(
|
||||
[this, mockHeight, &surfaceTags](const AZ::Aabb& inRegion, const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
[[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter)
|
||||
{
|
||||
ProcessRegionLoop(inRegion, stepSize, perPositionCallback, &surfaceTags, mockHeight);
|
||||
}
|
||||
);
|
||||
|
||||
AZStd::vector<Physics::HeightMaterialPoint> heightsAndMaterials;
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace UnitTest
|
||||
AZStd::unique_ptr<NiceMock<UnitTest::MockBoxShapeComponentRequests>> m_boxShapeRequests;
|
||||
AZStd::unique_ptr<NiceMock<UnitTest::MockShapeComponentRequests>> m_shapeRequests;
|
||||
AZStd::unique_ptr<NiceMock<UnitTest::MockTerrainAreaHeightRequests>> m_terrainAreaHeightRequests;
|
||||
AZStd::unique_ptr<NiceMock<UnitTest::MockTerrainAreaSurfaceRequestBus>> m_terrainAreaSurfaceRequests;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
@@ -84,6 +85,7 @@ namespace UnitTest
|
||||
m_boxShapeRequests.reset();
|
||||
m_shapeRequests.reset();
|
||||
m_terrainAreaHeightRequests.reset();
|
||||
m_terrainAreaSurfaceRequests.reset();
|
||||
m_app.Destroy();
|
||||
}
|
||||
|
||||
@@ -160,6 +162,49 @@ namespace UnitTest
|
||||
ActivateEntity(entity.get());
|
||||
return entity;
|
||||
}
|
||||
|
||||
void SetupSurfaceWeightMocks(AZ::Entity* entity, AzFramework::SurfaceData::SurfaceTagWeightList& expectedTags)
|
||||
{
|
||||
const SurfaceData::SurfaceTag tag1 = SurfaceData::SurfaceTag("tag1");
|
||||
const SurfaceData::SurfaceTag tag2 = SurfaceData::SurfaceTag("tag2");
|
||||
const SurfaceData::SurfaceTag tag3 = SurfaceData::SurfaceTag("tag3");
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeight tagWeight1;
|
||||
tagWeight1.m_surfaceType = tag1;
|
||||
tagWeight1.m_weight = 1.0f;
|
||||
expectedTags.push_back(tagWeight1);
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeight tagWeight2;
|
||||
tagWeight2.m_surfaceType = tag2;
|
||||
tagWeight2.m_weight = 0.7f;
|
||||
expectedTags.push_back(tagWeight2);
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeight tagWeight3;
|
||||
tagWeight3.m_surfaceType = tag3;
|
||||
tagWeight3.m_weight = 0.3f;
|
||||
expectedTags.push_back(tagWeight3);
|
||||
|
||||
m_terrainAreaSurfaceRequests = AZStd::make_unique<NiceMock<UnitTest::MockTerrainAreaSurfaceRequestBus>>(entity->GetId());
|
||||
ON_CALL(*m_terrainAreaSurfaceRequests, GetSurfaceWeights).WillByDefault(
|
||||
[tagWeight1, tagWeight2, tagWeight3](const AZ::Vector3& position, AzFramework::SurfaceData::SurfaceTagWeightList& surfaceWeights)
|
||||
{
|
||||
surfaceWeights.clear();
|
||||
float absYPos = fabsf(position.GetY());
|
||||
if (absYPos < 1.0f)
|
||||
{
|
||||
surfaceWeights.push_back(tagWeight1);
|
||||
}
|
||||
else if(absYPos < 2.0f)
|
||||
{
|
||||
surfaceWeights.push_back(tagWeight2);
|
||||
}
|
||||
else
|
||||
{
|
||||
surfaceWeights.push_back(tagWeight3);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(TerrainSystemTest, TrivialCreateDestroy)
|
||||
@@ -902,4 +947,100 @@ namespace UnitTest
|
||||
|
||||
terrainSystem->ProcessNormalsFromRegion(testRegionBox, stepSize, perPositionCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR);
|
||||
}
|
||||
|
||||
TEST_F(TerrainSystemTest, TerrainProcessSurfaceWeightsFromRegion)
|
||||
{
|
||||
const AZ::Aabb spawnerBox = AZ::Aabb::CreateFromMinMaxValues(-10.0f, -10.0f, -5.0f, 10.0f, 10.0f, 15.0f);
|
||||
auto entity = CreateAndActivateMockTerrainLayerSpawner(
|
||||
spawnerBox,
|
||||
[](AZ::Vector3& position, bool& terrainExists)
|
||||
{
|
||||
position.SetZ(1.0f);
|
||||
terrainExists = true;
|
||||
});
|
||||
|
||||
// Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 1 meter intervals.
|
||||
const AZ::Vector2 queryResolution(1.0f);
|
||||
auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution);
|
||||
|
||||
const AZ::Aabb testRegionBox = AZ::Aabb::CreateFromMinMaxValues(-3.0f, -3.0f, -1.0f, 3.0f, 3.0f, 1.0f);
|
||||
const AZ::Vector2 stepSize(1.0f);
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeightList expectedTags;
|
||||
SetupSurfaceWeightMocks(entity.get(), expectedTags);
|
||||
|
||||
auto perPositionCallback = [&expectedTags]([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex,
|
||||
const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
constexpr float epsilon = 0.0001f;
|
||||
float absYPos = fabsf(surfacePoint.m_position.GetY());
|
||||
if (absYPos < 1.0f)
|
||||
{
|
||||
EXPECT_EQ(surfacePoint.m_surfaceTags[0].m_surfaceType, expectedTags[0].m_surfaceType);
|
||||
EXPECT_NEAR(surfacePoint.m_surfaceTags[0].m_weight, expectedTags[0].m_weight, epsilon);
|
||||
}
|
||||
else if(absYPos < 2.0f)
|
||||
{
|
||||
EXPECT_EQ(surfacePoint.m_surfaceTags[0].m_surfaceType, expectedTags[1].m_surfaceType);
|
||||
EXPECT_NEAR(surfacePoint.m_surfaceTags[0].m_weight, expectedTags[1].m_weight, epsilon);
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_EQ(surfacePoint.m_surfaceTags[0].m_surfaceType, expectedTags[2].m_surfaceType);
|
||||
EXPECT_NEAR(surfacePoint.m_surfaceTags[0].m_weight, expectedTags[2].m_weight, epsilon);
|
||||
}
|
||||
};
|
||||
|
||||
terrainSystem->ProcessSurfaceWeightsFromRegion(testRegionBox, stepSize, perPositionCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR);
|
||||
}
|
||||
|
||||
TEST_F(TerrainSystemTest, TerrainProcessSurfacePointsFromRegion)
|
||||
{
|
||||
const AZ::Aabb spawnerBox = AZ::Aabb::CreateFromMinMaxValues(-10.0f, -10.0f, -5.0f, 10.0f, 10.0f, 15.0f);
|
||||
auto entity = CreateAndActivateMockTerrainLayerSpawner(
|
||||
spawnerBox,
|
||||
[](AZ::Vector3& position, bool& terrainExists)
|
||||
{
|
||||
position.SetZ(position.GetX() + position.GetY());
|
||||
terrainExists = true;
|
||||
});
|
||||
|
||||
// Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 1 meter intervals.
|
||||
const AZ::Vector2 queryResolution(1.0f);
|
||||
auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution);
|
||||
|
||||
const AZ::Aabb testRegionBox = AZ::Aabb::CreateFromMinMaxValues(-3.0f, -3.0f, -1.0f, 3.0f, 3.0f, 1.0f);
|
||||
const AZ::Vector2 stepSize(1.0f);
|
||||
|
||||
AzFramework::SurfaceData::SurfaceTagWeightList expectedTags;
|
||||
SetupSurfaceWeightMocks(entity.get(), expectedTags);
|
||||
|
||||
auto perPositionCallback = [&expectedTags]([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex,
|
||||
const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
constexpr float epsilon = 0.0001f;
|
||||
float expectedHeight = surfacePoint.m_position.GetX() + surfacePoint.m_position.GetY();
|
||||
|
||||
EXPECT_NEAR(surfacePoint.m_position.GetZ(), expectedHeight, epsilon);
|
||||
|
||||
float absYPos = fabsf(surfacePoint.m_position.GetY());
|
||||
if (absYPos < 1.0f)
|
||||
{
|
||||
EXPECT_EQ(surfacePoint.m_surfaceTags[0].m_surfaceType, expectedTags[0].m_surfaceType);
|
||||
EXPECT_NEAR(surfacePoint.m_surfaceTags[0].m_weight, expectedTags[0].m_weight, epsilon);
|
||||
}
|
||||
else if(absYPos < 2.0f)
|
||||
{
|
||||
EXPECT_EQ(surfacePoint.m_surfaceTags[0].m_surfaceType, expectedTags[1].m_surfaceType);
|
||||
EXPECT_NEAR(surfacePoint.m_surfaceTags[0].m_weight, expectedTags[1].m_weight, epsilon);
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_EQ(surfacePoint.m_surfaceTags[0].m_surfaceType, expectedTags[2].m_surfaceType);
|
||||
EXPECT_NEAR(surfacePoint.m_surfaceTags[0].m_weight, expectedTags[2].m_weight, epsilon);
|
||||
}
|
||||
};
|
||||
|
||||
terrainSystem->ProcessSurfacePointsFromRegion(testRegionBox, stepSize, perPositionCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT);
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
Reference in New Issue
Block a user