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:
Terry Michaels
2022-01-20 14:32:02 -06:00
committed by GitHub
11 changed files with 342 additions and 145 deletions
@@ -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, &sector]
(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