[Terrain] Optimize bulk queries to the Terrain System to retrieve height, surface weights, and normals (#7357)

monroegm-disable-blank-issue-2
amzn-sj 4 years ago committed by GitHub
parent dac3bc4ba6
commit 396ec8a247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -37,6 +37,7 @@ namespace AzFramework::SurfaceData
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AZStd::fixed_vector<SurfaceTagWeight, Constants::MaxSurfaceWeights>>();
serializeContext->Class<SurfacePoint>()
->Field("m_position", &SurfacePoint::m_position)
->Field("m_normal", &SurfacePoint::m_normal)
@ -46,6 +47,7 @@ namespace AzFramework::SurfaceData
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<AZStd::fixed_vector<SurfaceTagWeight, Constants::MaxSurfaceWeights>>();
behaviorContext->Class<SurfacePoint>("AzFramework::SurfaceData::SurfacePoint")
->Attribute(AZ::Script::Attributes::Category, "SurfaceData")
->Constructor()

@ -10,13 +10,19 @@
#include <AzCore/Math/Crc.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/fixed_vector.h>
namespace AzFramework::SurfaceData
{
namespace Constants
{
static constexpr const char* s_unassignedTagName = "(unassigned)";
//! The maximum number of surface weights that we can store.
//! For performance reasons, we want to limit this so that we can preallocate the max size in advance.
//! The current number is chosen to be higher than expected needs, but small enough to avoid being excessively wasteful.
//! (Dynamic structures would end up taking more memory than what we're preallocating)
static constexpr size_t MaxSurfaceWeights = 16;
}
struct SurfaceTagWeight
@ -70,7 +76,7 @@ namespace AzFramework::SurfaceData
}
};
using SurfaceTagWeightList = AZStd::vector<SurfaceTagWeight>;
using SurfaceTagWeightList = AZStd::fixed_vector<SurfaceTagWeight, Constants::MaxSurfaceWeights>;
struct SurfacePoint final
{

@ -29,12 +29,6 @@ namespace SurfaceData
class SurfaceTagWeights
{
public:
//! The maximum number of surface weights that we can store.
//! For performance reasons, we want to limit this so that we can preallocate the max size in advance.
//! The current number is chosen to be higher than expected needs, but small enough to avoid being excessively wasteful.
//! (Dynamic structures would end up taking more memory than what we're preallocating)
static inline constexpr size_t MaxSurfaceWeights = 16;
SurfaceTagWeights() = default;
//! Construct a collection of SurfaceTagWeights from the given SurfaceTagWeightList.
@ -65,7 +59,7 @@ namespace SurfaceData
// early-out once we pass the location for the entry instead of always searching every entry.
if (weightItr->m_surfaceType > tag)
{
if (m_weights.size() != MaxSurfaceWeights)
if (m_weights.size() != AzFramework::SurfaceData::Constants::MaxSurfaceWeights)
{
// We didn't find the surface type, so add the new entry in sorted order.
m_weights.insert(weightItr, { tag, weight });
@ -85,7 +79,7 @@ namespace SurfaceData
}
// We didn't find the surface weight, and the sort order for it is at the end, so add it to the back of the list.
if (m_weights.size() != MaxSurfaceWeights)
if (m_weights.size() != AzFramework::SurfaceData::Constants::MaxSurfaceWeights)
{
m_weights.emplace_back(tag, weight);
}
@ -188,7 +182,7 @@ namespace SurfaceData
//! @return The pointer to the tag that's found, or end() if it wasn't found.
const AzFramework::SurfaceData::SurfaceTagWeight* FindTag(AZ::Crc32 tag) const;
AZStd::fixed_vector<AzFramework::SurfaceData::SurfaceTagWeight, MaxSurfaceWeights> m_weights;
AZStd::fixed_vector<AzFramework::SurfaceData::SurfaceTagWeight, AzFramework::SurfaceData::Constants::MaxSurfaceWeights> m_weights;
};
//! SurfacePointList stores a collection of surface point data, which consists of positions, normals, and surface tag weights.

@ -42,7 +42,7 @@ namespace SurfaceData
AzFramework::SurfaceData::SurfaceTagWeightList SurfaceTagWeights::GetSurfaceTagWeightList() const
{
AzFramework::SurfaceData::SurfaceTagWeightList weights;
weights.reserve(m_weights.size());
for (auto& weight : m_weights)
{
weights.emplace_back(weight);

@ -275,7 +275,7 @@ namespace UnitTest
{
AZ_PROFILE_FUNCTION(Entity);
AZ::Crc32 tags[SurfaceData::SurfaceTagWeights::MaxSurfaceWeights];
AZ::Crc32 tags[AzFramework::SurfaceData::Constants::MaxSurfaceWeights];
AZ::SimpleLcgRandom randomGenerator(1234567);
// Declare this outside the loop so that we aren't benchmarking creation and destruction.
@ -316,7 +316,7 @@ namespace UnitTest
{
AZ_PROFILE_FUNCTION(Entity);
AZ::Crc32 tags[SurfaceData::SurfaceTagWeights::MaxSurfaceWeights];
AZ::Crc32 tags[AzFramework::SurfaceData::Constants::MaxSurfaceWeights];
AZ::SimpleLcgRandom randomGenerator(1234567);
// Declare this outside the loop so that we aren't benchmarking creation and destruction.

@ -177,6 +177,193 @@ bool TerrainSystem::InWorldBounds(float x, float y) const
return false;
}
// Generate positions to be queried based on the sampler type.
void TerrainSystem::GenerateQueryPositions(const AZStd::span<AZ::Vector3>& inPositions,
AZStd::vector<AZ::Vector3>& outPositions,
Sampler sampler) const
{
const float minHeight = m_currentSettings.m_worldBounds.GetMin().GetZ();
for (auto& position : inPositions)
{
switch(sampler)
{
case AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR:
{
AZ::Vector2 normalizedDelta;
AZ::Vector2 pos0;
ClampPosition(position.GetX(), position.GetY(), pos0, normalizedDelta);
const AZ::Vector2 pos1(pos0.GetX() + m_currentSettings.m_heightQueryResolution,
pos0.GetY() + m_currentSettings.m_heightQueryResolution);
outPositions.emplace_back(AZ::Vector3(pos0.GetX(), pos0.GetY(), minHeight));
outPositions.emplace_back(AZ::Vector3(pos1.GetX(), pos0.GetY(), minHeight));
outPositions.emplace_back(AZ::Vector3(pos0.GetX(), pos1.GetY(), minHeight));
outPositions.emplace_back(AZ::Vector3(pos1.GetX(), pos1.GetY(), minHeight));
}
break;
case AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP:
{
AZ::Vector2 normalizedDelta;
AZ::Vector2 clampedPosition;
ClampPosition(position.GetX(), position.GetY(), clampedPosition, normalizedDelta);
outPositions.emplace_back(AZ::Vector3(clampedPosition.GetX(), clampedPosition.GetY(), minHeight));
}
break;
case AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT:
[[fallthrough]];
default:
outPositions.emplace_back(AZ::Vector3(position.GetX(), position.GetY(), minHeight));
break;
}
}
}
AZStd::vector<AZ::Vector3> TerrainSystem::GenerateInputPositionsFromRegion(
const AZ::Aabb& inRegion,
const AZ::Vector2& stepSize) const
{
AZStd::vector<AZ::Vector3> inPositions;
const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize);
inPositions.reserve(numSamplesX * numSamplesY);
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++)
{
float fx = aznumeric_cast<float>(inRegion.GetMin().GetX() + (x * stepSize.GetX()));
inPositions.emplace_back(AZ::Vector3(fx, fy, 0.0f));
}
}
return inPositions;
}
void TerrainSystem::MakeBulkQueries(
const AZStd::span<AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
BulkQueriesCallback queryCallback) const
{
AZ::Aabb bounds;
AZ::EntityId prevAreaId = FindBestAreaEntityAtPosition(inPositions[0].GetX(), inPositions[0].GetY(), bounds);
// We use a sliding window here and update the window end for each
// position that falls in the same area as the previous positions. This consumes lesser memory
// than sorting the points into separate lists and handling putting them back together.
// This may be sub optimal if the points are randomly distributed in the list as opposed
// to points in the same area id being close to each other.
size_t windowStart = 0;
size_t windowEnd = 0;
const size_t numPositions = inPositions.size();
for(int i = 1; i < numPositions; i++)
{
AZ::EntityId areaId = FindBestAreaEntityAtPosition(inPositions[i].GetX(), inPositions[i].GetY(), bounds);
bool queryHeights = false;
if (areaId == prevAreaId)
{
// Update window end to current position.
// If it's the last position, submit the query.
windowEnd = i;
if (windowEnd == numPositions - 1)
{
queryHeights = true;
}
}
else
{
queryHeights = true;
}
if (queryHeights)
{
// If the area id is a default entity id, it usually means the
// position is outside world bounds.
if (prevAreaId != AZ::EntityId())
{
size_t spanLength = (windowEnd - windowStart) + 1;
queryCallback(AZStd::span<AZ::Vector3>(inPositions.begin() + windowStart, spanLength),
AZStd::span<AZ::Vector3>(outPositions.begin() + windowStart, spanLength),
AZStd::span<bool>(outTerrainExists.begin() + windowStart, spanLength),
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList>(outSurfaceWeights.begin() + windowStart, spanLength),
prevAreaId);
}
// Reset the window to start at the current position. Set the new area
// id on which to run the next query.
windowStart = windowEnd = i;
prevAreaId = areaId;
}
}
}
void TerrainSystem::GetHeightsSynchronous(const AZStd::span<AZ::Vector3>& inPositions, Sampler sampler,
AZStd::span<float> heights, AZStd::span<bool> terrainExists) const
{
AZStd::shared_lock<AZStd::shared_mutex> lock(m_areaMutex);
AZStd::vector<AZ::Vector3> outPositions;
AZStd::vector<bool> outTerrainExists;
// outPositions holds the iterators to results of the bulk queries.
// In the case of the bilinear sampler, we'll be making 4 queries per
// input position.
size_t indexStepSize = (sampler == AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) ? 4 : 1;
outPositions.reserve(inPositions.size() * indexStepSize);
outTerrainExists.resize(inPositions.size() * indexStepSize);
GenerateQueryPositions(inPositions, outPositions, sampler);
auto callback = []([[maybe_unused]] const AZStd::span<AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
[[maybe_unused]] AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
AZ::EntityId areaId)
{
AZ_Assert((inPositions.size() == outPositions.size() && inPositions.size() == outTerrainExists.size()),
"The sizes of the terrain exists list and in/out positions list should match.");
Terrain::TerrainAreaHeightRequestBus::Event(areaId, &Terrain::TerrainAreaHeightRequestBus::Events::GetHeights,
outPositions, outTerrainExists);
};
// This will be unused for heights. It's fine if it's empty.
AZStd::vector<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights;
MakeBulkQueries(outPositions, outPositions, outTerrainExists, outSurfaceWeights, callback);
// Compute/store the final result
for (size_t i = 0, iteratorIndex = 0; i < inPositions.size(); i++, iteratorIndex += indexStepSize)
{
switch(sampler)
{
case AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR:
{
// We now need to compute the final height after all the bulk queries are done.
AZ::Vector2 normalizedDelta;
AZ::Vector2 clampedPosition;
ClampPosition(inPositions[i].GetX(), inPositions[i].GetY(), clampedPosition, normalizedDelta);
const float heightX0Y0 = outPositions[iteratorIndex].GetZ();
const float heightX1Y0 = outPositions[iteratorIndex + 1].GetZ();
const float heightX0Y1 = outPositions[iteratorIndex + 2].GetZ();
const float heightX1Y1 = outPositions[iteratorIndex + 3].GetZ();
const float heightXY0 = AZ::Lerp(heightX0Y0, heightX1Y0, normalizedDelta.GetX());
const float heightXY1 = AZ::Lerp(heightX0Y1, heightX1Y1, normalizedDelta.GetX());
heights[i] = AZ::Lerp(heightXY0, heightXY1, normalizedDelta.GetY());
terrainExists[i] = outTerrainExists[iteratorIndex];
}
break;
case AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP:
[[fallthrough]];
case AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT:
[[fallthrough]];
default:
// For clamp and exact, we just need to store the results of the bulk query.
heights[i] = outPositions[iteratorIndex].GetZ();
terrainExists[i] = outTerrainExists[iteratorIndex];
break;
}
}
}
float TerrainSystem::GetHeightSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const
{
bool terrainExists = false;
@ -315,6 +502,39 @@ bool TerrainSystem::GetIsHoleFromFloats(float x, float y, Sampler sampler) const
return !terrainExists;
}
void TerrainSystem::GetNormalsSynchronous(const AZStd::span<AZ::Vector3>& inPositions, Sampler sampler,
AZStd::span<AZ::Vector3> normals, AZStd::span<bool> terrainExists) const
{
AZStd::vector<AZ::Vector3> directionVectors;
directionVectors.reserve(inPositions.size() * 4);
const AZ::Vector2 range(m_currentSettings.m_heightQueryResolution / 2.0f, m_currentSettings.m_heightQueryResolution / 2.0f);
size_t indexStepSize = 4;
for (auto& position : inPositions)
{
directionVectors.emplace_back(position.GetX(), position.GetY() - range.GetY(), 0.0f);
directionVectors.emplace_back(position.GetX() - range.GetX(), position.GetY(), 0.0f);
directionVectors.emplace_back(position.GetX() + range.GetX(), position.GetY(), 0.0f);
directionVectors.emplace_back(position.GetX(), position.GetY() + range.GetY(), 0.0f);
}
AZStd::vector<float> heights(directionVectors.size());
AZStd::vector<bool> exists(directionVectors.size());
GetHeightsSynchronous(directionVectors, sampler, heights, exists);
for (size_t i = 0, iteratorIndex = 0; i < inPositions.size(); i++, iteratorIndex += indexStepSize)
{
directionVectors[iteratorIndex].SetZ(heights[iteratorIndex]);
directionVectors[iteratorIndex + 1].SetZ(heights[iteratorIndex + 1]);
directionVectors[iteratorIndex + 2].SetZ(heights[iteratorIndex + 2]);
directionVectors[iteratorIndex + 3].SetZ(heights[iteratorIndex + 3]);
normals[i] = (directionVectors[iteratorIndex + 2] - directionVectors[iteratorIndex + 1]).
Cross(directionVectors[iteratorIndex + 3] - directionVectors[iteratorIndex]).GetNormalized();
terrainExists[i] = exists[iteratorIndex];
}
}
AZ::Vector3 TerrainSystem::GetNormalSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const
{
AZStd::shared_lock<AZStd::shared_mutex> lock(m_areaMutex);
@ -471,6 +691,35 @@ AZ::EntityId TerrainSystem::FindBestAreaEntityAtPosition(float x, float y, AZ::A
return AZ::EntityId();
}
void TerrainSystem::GetOrderedSurfaceWeightsFromList(
const AZStd::span<AZ::Vector3>& inPositions,
[[maybe_unused]] Sampler sampler,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList,
AZStd::span<bool> terrainExists) const
{
if (terrainExists.size() == outSurfaceWeightsList.size())
{
AZStd::vector<float> heights(inPositions.size());
GetHeightsSynchronous(inPositions, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, heights, terrainExists);
}
auto callback = [](const AZStd::span<AZ::Vector3> inPositions,
[[maybe_unused]] AZStd::span<AZ::Vector3> outPositions,
[[maybe_unused]] AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
AZ::EntityId areaId)
{
AZ_Assert(inPositions.size() == outSurfaceWeights.size(),
"The sizes of the surface weights list and in/out positions list should match.");
Terrain::TerrainAreaSurfaceRequestBus::Event(areaId, &Terrain::TerrainAreaSurfaceRequestBus::Events::GetSurfaceWeightsFromList,
inPositions, outSurfaceWeights);
};
// This will be unused for surface weights. It's fine if it's empty.
AZStd::vector<AZ::Vector3> outPositions;
MakeBulkQueries(inPositions, outPositions, terrainExists, outSurfaceWeightsList, callback);
}
void TerrainSystem::GetOrderedSurfaceWeights(
const float x,
const float y,
@ -551,13 +800,17 @@ void TerrainSystem::ProcessHeightsFromList(
return;
}
AZStd::vector<bool> terrainExists(inPositions.size());
AZStd::vector<float> heights(inPositions.size());
GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (const auto& position : inPositions)
for (size_t i = 0; i < inPositions.size(); i++)
{
bool terrainExists = false;
surfacePoint.m_position = position;
surfacePoint.m_position.SetZ(GetHeight(position, sampleFilter, &terrainExists));
perPositionCallback(surfacePoint, terrainExists);
surfacePoint.m_position = inPositions[i];
surfacePoint.m_position.SetZ(heights[i]);
perPositionCallback(surfacePoint, terrainExists[i]);
}
}
@ -571,13 +824,17 @@ void TerrainSystem::ProcessNormalsFromList(
return;
}
AZStd::vector<bool> terrainExists(inPositions.size());
AZStd::vector<AZ::Vector3> normals(inPositions.size());
GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (const auto& position : inPositions)
for (size_t i = 0; i < inPositions.size(); i++)
{
bool terrainExists = false;
surfacePoint.m_position = position;
surfacePoint.m_normal = GetNormal(position, sampleFilter, &terrainExists);
perPositionCallback(surfacePoint, terrainExists);
surfacePoint.m_position = inPositions[i];
surfacePoint.m_normal = AZStd::move(normals[i]);
perPositionCallback(surfacePoint, terrainExists[i]);
}
}
@ -591,13 +848,17 @@ void TerrainSystem::ProcessSurfaceWeightsFromList(
return;
}
AZStd::vector<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList(inPositions.size());
AZStd::vector<bool> terrainExists(inPositions.size());
GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (const auto& position : inPositions)
for (size_t i = 0; i < inPositions.size(); i++)
{
bool terrainExists = false;
surfacePoint.m_position = position;
GetSurfaceWeights(position, surfacePoint.m_surfaceTags, sampleFilter, &terrainExists);
perPositionCallback(surfacePoint, terrainExists);
surfacePoint.m_position = inPositions[i];
surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]);
perPositionCallback(surfacePoint, terrainExists[i]);
}
}
@ -611,13 +872,26 @@ void TerrainSystem::ProcessSurfacePointsFromList(
return;
}
AZStd::vector<float> heights(inPositions.size());
AZStd::vector<AZ::Vector3> normals(inPositions.size());
AZStd::vector<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList(inPositions.size());
AZStd::vector<bool> terrainExists(inPositions.size());
GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists);
GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists);
// We can skip the unnecessary call to GetHeights since we already
// got the terrain exists flags in the earlier call to GetHeights
AZStd::vector<bool> terrainExistsEmpty;
GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExistsEmpty);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (const auto& position : inPositions)
for (size_t i = 0; i < inPositions.size(); i++)
{
bool terrainExists = false;
surfacePoint.m_position = position;
GetSurfacePoint(position, surfacePoint, sampleFilter, &terrainExists);
perPositionCallback(surfacePoint, terrainExists);
surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), heights[i]);
surfacePoint.m_normal = AZStd::move(normals[i]);
surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]);
perPositionCallback(surfacePoint, terrainExists[i]);
}
}
@ -723,20 +997,23 @@ void TerrainSystem::ProcessHeightsFromRegion(
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()));
const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize);
AZStd::vector<AZ::Vector3> inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize);
AZStd::vector<bool> terrainExists(inPositions.size());
AZStd::vector<float> heights(inPositions.size());
GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (size_t y = 0; y < numSamplesY; y++)
for (size_t y = 0, i = 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, 0.0f);
surfacePoint.m_position.SetZ(GetHeight(surfacePoint.m_position, sampleFilter, &terrainExists));
perPositionCallback(x, y, surfacePoint, terrainExists);
surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), heights[i]);
perPositionCallback(x, y, surfacePoint, terrainExists[i]);
i++;
}
}
}
@ -753,20 +1030,24 @@ void TerrainSystem::ProcessNormalsFromRegion(
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()));
const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize);
AZStd::vector<AZ::Vector3> inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize);
AZStd::vector<bool> terrainExists(inPositions.size());
AZStd::vector<AZ::Vector3> normals(inPositions.size());
GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (size_t y = 0; y < numSamplesY; y++)
for (size_t y = 0, i = 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, 0.0f);
surfacePoint.m_normal = GetNormal(surfacePoint.m_position, sampleFilter, &terrainExists);
perPositionCallback(x, y, surfacePoint, terrainExists);
surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), 0.0f);
surfacePoint.m_normal = AZStd::move(normals[i]);
perPositionCallback(x, y, surfacePoint, terrainExists[i]);
i++;
}
}
}
@ -783,20 +1064,24 @@ void TerrainSystem::ProcessSurfaceWeightsFromRegion(
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()));
const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize);
AZStd::vector<AZ::Vector3> inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize);
AZStd::vector<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList(inPositions.size());
AZStd::vector<bool> terrainExists(inPositions.size());
GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (size_t y = 0; y < numSamplesY; y++)
for (size_t y = 0, i = 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, 0.0f);
GetSurfaceWeights(surfacePoint.m_position, surfacePoint.m_surfaceTags, sampleFilter, &terrainExists);
perPositionCallback(x, y, surfacePoint, terrainExists);
surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), 0.0f);
surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]);
perPositionCallback(x, y, surfacePoint, terrainExists[i]);
i++;
}
}
}
@ -813,20 +1098,33 @@ void TerrainSystem::ProcessSurfacePointsFromRegion(
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()));
const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize);
AZStd::vector<AZ::Vector3> inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize);
AZStd::vector<float> heights(inPositions.size());
AZStd::vector<AZ::Vector3> normals(inPositions.size());
AZStd::vector<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList(inPositions.size());
AZStd::vector<bool> terrainExists(inPositions.size());
GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists);
GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists);
// We can skip the unnecessary call to GetHeights since we already
// got the terrain exists flags in the earlier call to GetHeights
AZStd::vector<bool> terrainExistsEmpty;
GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExistsEmpty);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (size_t y = 0; y < numSamplesY; y++)
for (size_t y = 0, i = 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, 0.0f);
GetSurfacePoint(surfacePoint.m_position, surfacePoint, sampleFilter, &terrainExists);
perPositionCallback(x, y, surfacePoint, terrainExists);
surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), heights[i]);
surfacePoint.m_normal = AZStd::move(normals[i]);
surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]);
perPositionCallback(x, y, surfacePoint, terrainExists[i]);
i++;
}
}
}

@ -207,6 +207,38 @@ namespace Terrain
float GetTerrainAreaHeight(float x, float y, bool& terrainExists) const;
AZ::Vector3 GetNormalSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const;
typedef AZStd::function<void(
const AZStd::span<AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
AZ::EntityId areaId)> BulkQueriesCallback;
void GetHeightsSynchronous(
const AZStd::span<AZ::Vector3>& inPositions,
Sampler sampler, AZStd::span<float> heights,
AZStd::span<bool> terrainExists) const;
void GetNormalsSynchronous(
const AZStd::span<AZ::Vector3>& inPositions,
Sampler sampler, AZStd::span<AZ::Vector3> normals,
AZStd::span<bool> terrainExists) const;
void GetOrderedSurfaceWeightsFromList(
const AZStd::span<AZ::Vector3>& inPositions, Sampler sampler,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList,
AZStd::span<bool> terrainExists) const;
void MakeBulkQueries(
const AZStd::span<AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWieghts,
BulkQueriesCallback queryCallback) const;
void GenerateQueryPositions(const AZStd::span<AZ::Vector3>& inPositions,
AZStd::vector<AZ::Vector3>& outPositions,
Sampler sampler) const;
AZStd::vector<AZ::Vector3> GenerateInputPositionsFromRegion(
const AZ::Aabb& inRegion,
const AZ::Vector2& stepSize) const;
// AZ::TickBus::Handler overrides ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;

@ -158,6 +158,15 @@ namespace UnitTest
// Let the test function modify these values based on the needs of the specific test.
mockHeights(outPosition, terrainExists);
});
ON_CALL(*m_terrainAreaHeightRequests, GetHeights)
.WillByDefault(
[mockHeights](AZStd::span<AZ::Vector3> inOutPositionList, AZStd::span<bool> terrainExistsList)
{
for (int i = 0; i < inOutPositionList.size(); i++)
{
mockHeights(inOutPositionList[i], terrainExistsList[i]);
}
});
ActivateEntity(entity.get());
return entity;
@ -184,9 +193,9 @@ namespace UnitTest
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)
auto mockGetSurfaceWeights = [tagWeight1, tagWeight2, tagWeight3](
const AZ::Vector3& position,
AzFramework::SurfaceData::SurfaceTagWeightList& surfaceWeights)
{
surfaceWeights.clear();
float absYPos = fabsf(position.GetY());
@ -202,6 +211,19 @@ namespace UnitTest
{
surfaceWeights.push_back(tagWeight3);
}
};
m_terrainAreaSurfaceRequests = AZStd::make_unique<NiceMock<UnitTest::MockTerrainAreaSurfaceRequestBus>>(entity->GetId());
ON_CALL(*m_terrainAreaSurfaceRequests, GetSurfaceWeights).WillByDefault(mockGetSurfaceWeights);
ON_CALL(*m_terrainAreaSurfaceRequests, GetSurfaceWeightsFromList).WillByDefault(
[mockGetSurfaceWeights](
AZStd::span<const AZ::Vector3> inPositionList,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList)
{
for (size_t i = 0; i < inPositionList.size(); i++)
{
mockGetSurfaceWeights(inPositionList[i], outSurfaceWeightsList[i]);
}
}
);
}

Loading…
Cancel
Save