d0e5f9d20b
* Added Async APIs for the various Process*FromList terrain functions. Please note that we are currently defaulting the number of worker threads to one, because splitting the work over multiple threads causes contention when locking various mutexes, resulting in slower overall wall time for async requests split over multiple threads vs one where all the work is done on a single thread. The latter is still preferable over a regular synchronous call because it is just as quick and prevents the main thread from blocking. This should be changed once the mutex contention issues have been addressed, so that async calls automatically split the work between available job manager worker threads, unless the ProcessAsyncParams specify a different desired number of jobs. Signed-off-by: bosnichd <bosnichd@amazon.com> * Fix Linux builds by adding missing #include Signed-off-by: bosnichd <bosnichd@amazon.com> * Added a test for cancellation of terrain async requests, and fix it so that it works. Note that the benchmarks show this implementation to be slightly slower than the previous one, which I presume is because we're now calling a 'perSurfacePointFunction' in the inner loop; this can probably be addressed, but will result in a lot of code duplication, and I think efforts will be better spent on removing the mutex contention to enable running multiple terrain async jobs at the same time. Signed-off-by: bosnichd <bosnichd@amazon.com> * Added Async versions for all Process*Region terrain API functions, along with benchmarks. Signed-off-by: bosnichd <bosnichd@amazon.com> * Fix the newly added terrain async request benchmarks to actually use the async APIs. Signed-off-by: bosnichd <bosnichd@amazon.com> * Revert to the original version which just calls the synchronous API from the job function, along with some other updates in response to review feedback. Signed-off-by: bosnichd <bosnichd@amazon.com> * Change the TerrainWorldDebugger to use the async API, along with the following changes: - TerrainJobContext no longer uses a JobCancelGroup so we can guarantee the completion callbacks of associated jobs will be invoked even if it is cancelled. - As a result of the above change, the ProcessAsyncCompleteCallback function signature again accepts the associated TerrainJobContext as a param. - The TerrainProcessAsyncCancellation test has been resurrected and simplified by using binary semaphores instead of condition variables. - All the async related TerrainSystemBenchmark functions have been simplified by using binary semaphores instead of condition variables. - Global cancellation of all terrain jobs on deactivation of the TerrainSystem has been reintroduced, but in a different way than before. - Other miscellaneous changes/fixes made while testing and based on earlier PR feedback. Signed-off-by: bosnichd <bosnichd@amazon.com> * Updates based on review feedback: - Go back to using a vector instead of an array (fixed the original problem by adding custom copy/assignment constructors/operators to the WireframeSector struct). - When calling WireframeSector::Reset, block until any associated in flight has completed. - Added the concept of a minimum number of positions per terrain job. Signed-off-by: bosnichd <bosnichd@amazon.com> * Use semaphore instead of binary_semaphore in a bunch of places to account for the race condition where a completion callback fires before we started waiting for it. Signed-off-by: bosnichd <bosnichd@amazon.com>
1405 lines
57 KiB
C++
1405 lines
57 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <TerrainSystem/TerrainSystem.h>
|
|
#include <AzCore/std/parallel/shared_mutex.h>
|
|
#include <AzCore/std/sort.h>
|
|
#include <SurfaceData/SurfaceDataTypes.h>
|
|
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
|
|
#include <LmbrCentral/Shape/ShapeComponentBus.h>
|
|
|
|
#include <Atom/RPI.Public/Scene.h>
|
|
#include <Atom/RPI.Public/FeatureProcessorFactory.h>
|
|
#include <TerrainRenderer/TerrainFeatureProcessor.h>
|
|
|
|
#include <Terrain/Ebuses/TerrainAreaSurfaceRequestBus.h>
|
|
|
|
using namespace Terrain;
|
|
|
|
bool TerrainLayerPriorityComparator::operator()(const AZ::EntityId& layer1id, const AZ::EntityId& layer2id) const
|
|
{
|
|
// Comparator for insertion/keylookup.
|
|
// Sorts into layer/priority order, highest priority first.
|
|
AZ::u32 priority1 = 0;
|
|
AZ::u32 layer1 = 0;
|
|
Terrain::TerrainSpawnerRequestBus::Event(layer1id, &Terrain::TerrainSpawnerRequestBus::Events::GetPriority, layer1, priority1);
|
|
|
|
AZ::u32 priority2 = 0;
|
|
AZ::u32 layer2 = 0;
|
|
Terrain::TerrainSpawnerRequestBus::Event(layer2id, &Terrain::TerrainSpawnerRequestBus::Events::GetPriority, layer2, priority2);
|
|
|
|
if (layer1 < layer2)
|
|
{
|
|
return false;
|
|
}
|
|
else if (layer1 > layer2)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (priority1 != priority2)
|
|
{
|
|
return priority1 > priority2;
|
|
}
|
|
|
|
return layer1id > layer2id;
|
|
}
|
|
|
|
TerrainSystem::TerrainSystem()
|
|
: m_terrainRaycastContext(*this)
|
|
{
|
|
Terrain::TerrainSystemServiceRequestBus::Handler::BusConnect();
|
|
AZ::TickBus::Handler::BusConnect();
|
|
|
|
m_currentSettings.m_systemActive = false;
|
|
m_currentSettings.m_worldBounds = AZ::Aabb::CreateNull();
|
|
|
|
m_requestedSettings = m_currentSettings;
|
|
m_requestedSettings.m_worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-512.0f), AZ::Vector3(512.0f));
|
|
|
|
// Use the global JobManager for terrain jobs (we could create our own dedicated terrain JobManager if needed).
|
|
AZ::JobManagerBus::BroadcastResult(m_terrainJobManager, &AZ::JobManagerEvents::GetManager);
|
|
AZ_Assert(m_terrainJobManager, "No global JobManager found.");
|
|
}
|
|
|
|
TerrainSystem::~TerrainSystem()
|
|
{
|
|
AZ::TickBus::Handler::BusDisconnect();
|
|
Terrain::TerrainSystemServiceRequestBus::Handler::BusDisconnect();
|
|
|
|
Deactivate();
|
|
}
|
|
|
|
void TerrainSystem::Activate()
|
|
{
|
|
AzFramework::Terrain::TerrainDataNotificationBus::Broadcast(
|
|
&AzFramework::Terrain::TerrainDataNotificationBus::Events::OnTerrainDataCreateBegin);
|
|
|
|
m_dirtyRegion = AZ::Aabb::CreateNull();
|
|
m_terrainHeightDirty = true;
|
|
m_terrainSettingsDirty = true;
|
|
m_terrainSurfacesDirty = true;
|
|
m_requestedSettings.m_systemActive = true;
|
|
|
|
{
|
|
AZStd::unique_lock<AZStd::shared_mutex> lock(m_areaMutex);
|
|
m_registeredAreas.clear();
|
|
}
|
|
|
|
AzFramework::Terrain::TerrainDataRequestBus::Handler::BusConnect();
|
|
|
|
// Register any terrain spawners that were already active before the terrain system activated.
|
|
auto enumerationCallback = [&]([[maybe_unused]] Terrain::TerrainSpawnerRequests* terrainSpawner) -> bool
|
|
{
|
|
AZ::EntityId areaId = *(Terrain::TerrainSpawnerRequestBus::GetCurrentBusId());
|
|
RegisterArea(areaId);
|
|
|
|
// Keep Enumerating
|
|
return true;
|
|
};
|
|
Terrain::TerrainSpawnerRequestBus::EnumerateHandlers(enumerationCallback);
|
|
|
|
AzFramework::Terrain::TerrainDataNotificationBus::Broadcast(
|
|
&AzFramework::Terrain::TerrainDataNotificationBus::Events::OnTerrainDataCreateEnd);
|
|
}
|
|
|
|
void TerrainSystem::Deactivate()
|
|
{
|
|
{
|
|
// Cancel all active terrain jobs, and wait until they have completed.
|
|
AZStd::unique_lock<AZStd::mutex> lock(m_activeTerrainJobContextMutex);
|
|
for (auto activeTerrainJobContext : m_activeTerrainJobContexts)
|
|
{
|
|
activeTerrainJobContext->Cancel();
|
|
}
|
|
m_activeTerrainJobContextMutexConditionVariable.wait(lock, [this]{ return m_activeTerrainJobContexts.empty(); });
|
|
}
|
|
|
|
// Stop listening to the bus even before we signal DestroyBegin so that way any calls to the terrain system as a *result* of
|
|
// calling DestroyBegin will fail to reach the terrain system.
|
|
AzFramework::Terrain::TerrainDataRequestBus::Handler::BusDisconnect();
|
|
|
|
AzFramework::Terrain::TerrainDataNotificationBus::Broadcast(
|
|
&AzFramework::Terrain::TerrainDataNotificationBus::Events::OnTerrainDataDestroyBegin);
|
|
|
|
{
|
|
AZStd::unique_lock<AZStd::shared_mutex> lock(m_areaMutex);
|
|
m_registeredAreas.clear();
|
|
}
|
|
|
|
m_dirtyRegion = AZ::Aabb::CreateNull();
|
|
m_terrainHeightDirty = true;
|
|
m_terrainSettingsDirty = true;
|
|
m_terrainSurfacesDirty = true;
|
|
m_requestedSettings.m_systemActive = false;
|
|
|
|
AzFramework::Terrain::TerrainDataNotificationBus::Broadcast(
|
|
&AzFramework::Terrain::TerrainDataNotificationBus::Events::OnTerrainDataDestroyEnd);
|
|
}
|
|
|
|
void TerrainSystem::SetTerrainAabb(const AZ::Aabb& worldBounds)
|
|
{
|
|
m_requestedSettings.m_worldBounds = worldBounds;
|
|
m_terrainSettingsDirty = true;
|
|
}
|
|
|
|
void TerrainSystem::SetTerrainHeightQueryResolution(float queryResolution)
|
|
{
|
|
m_requestedSettings.m_heightQueryResolution = queryResolution;
|
|
m_terrainSettingsDirty = true;
|
|
}
|
|
|
|
AZ::Aabb TerrainSystem::GetTerrainAabb() const
|
|
{
|
|
return m_currentSettings.m_worldBounds;
|
|
}
|
|
|
|
float TerrainSystem::GetTerrainHeightQueryResolution() const
|
|
{
|
|
return m_currentSettings.m_heightQueryResolution;
|
|
}
|
|
|
|
void TerrainSystem::ClampPosition(float x, float y, AZ::Vector2& outPosition, AZ::Vector2& normalizedDelta) const
|
|
{
|
|
// Given an input position, clamp the values to our terrain grid, where it will always go to the terrain grid point
|
|
// at a lower value, whether positive or negative. Ex: 3.3 -> 3, -3.3 -> -4
|
|
// Also, return the normalized delta as a value of [0-1) describing what fraction of a grid point the value moved.
|
|
|
|
// Scale the position by the query resolution, so that integer values represent exact steps on the grid,
|
|
// and fractional values are the amount in-between each grid point, in the range [0-1).
|
|
AZ::Vector2 normalizedPosition = AZ::Vector2(x, y) / m_currentSettings.m_heightQueryResolution;
|
|
normalizedDelta = AZ::Vector2(
|
|
normalizedPosition.GetX() - floor(normalizedPosition.GetX()), normalizedPosition.GetY() - floor(normalizedPosition.GetY()));
|
|
|
|
// Remove the fractional part, then scale back down into world space.
|
|
outPosition = (normalizedPosition - normalizedDelta) * m_currentSettings.m_heightQueryResolution;
|
|
}
|
|
|
|
bool TerrainSystem::InWorldBounds(float x, float y) const
|
|
{
|
|
const float zTestValue = m_currentSettings.m_worldBounds.GetMin().GetZ();
|
|
const AZ::Vector3 testValue{ x, y, zTestValue };
|
|
if (m_currentSettings.m_worldBounds.Contains(testValue))
|
|
{
|
|
return true;
|
|
}
|
|
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;
|
|
float height = m_currentSettings.m_worldBounds.GetMin().GetZ();
|
|
|
|
if (!InWorldBounds(x, y))
|
|
{
|
|
if (terrainExistsPtr)
|
|
{
|
|
*terrainExistsPtr = terrainExists;
|
|
return height;
|
|
}
|
|
}
|
|
|
|
AZStd::shared_lock<AZStd::shared_mutex> lock(m_areaMutex);
|
|
|
|
switch (sampler)
|
|
{
|
|
// Get the value at the requested location, using the terrain grid to bilinear filter between sample grid points.
|
|
case AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR:
|
|
{
|
|
// pos0 contains one corner of our grid square, pos1 contains the opposite corner, and normalizedDelta is the fractional
|
|
// amount the position exists between those corners.
|
|
// Ex: (3.3, 4.4) would have a pos0 of (3, 4), a pos1 of (4, 5), and a delta of (0.3, 0.4).
|
|
AZ::Vector2 normalizedDelta;
|
|
AZ::Vector2 pos0;
|
|
ClampPosition(x, y, pos0, normalizedDelta);
|
|
const AZ::Vector2 pos1 = pos0 + AZ::Vector2(m_currentSettings.m_heightQueryResolution);
|
|
|
|
const float heightX0Y0 = GetTerrainAreaHeight(pos0.GetX(), pos0.GetY(), terrainExists);
|
|
const float heightX1Y0 = GetTerrainAreaHeight(pos1.GetX(), pos0.GetY(), terrainExists);
|
|
const float heightX0Y1 = GetTerrainAreaHeight(pos0.GetX(), pos1.GetY(), terrainExists);
|
|
const float heightX1Y1 = GetTerrainAreaHeight(pos1.GetX(), pos1.GetY(), terrainExists);
|
|
const float heightXY0 = AZ::Lerp(heightX0Y0, heightX1Y0, normalizedDelta.GetX());
|
|
const float heightXY1 = AZ::Lerp(heightX0Y1, heightX1Y1, normalizedDelta.GetX());
|
|
height = AZ::Lerp(heightXY0, heightXY1, normalizedDelta.GetY());
|
|
}
|
|
break;
|
|
|
|
//! Clamp the input point to the terrain sample grid, then get the height at the given grid location.
|
|
case AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP:
|
|
{
|
|
AZ::Vector2 normalizedDelta;
|
|
AZ::Vector2 clampedPosition;
|
|
ClampPosition(x, y, clampedPosition, normalizedDelta);
|
|
|
|
height = GetTerrainAreaHeight(clampedPosition.GetX(), clampedPosition.GetY(), terrainExists);
|
|
}
|
|
break;
|
|
|
|
//! Directly get the value at the location, regardless of terrain sample grid density.
|
|
case AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT:
|
|
[[fallthrough]];
|
|
default:
|
|
height = GetTerrainAreaHeight(x, y, terrainExists);
|
|
break;
|
|
}
|
|
|
|
// For now, always set terrainExists to true, as we don't have a way to author data for terrain holes yet.
|
|
if (terrainExistsPtr)
|
|
{
|
|
*terrainExistsPtr = terrainExists;
|
|
}
|
|
|
|
return AZ::GetClamp(
|
|
height, m_currentSettings.m_worldBounds.GetMin().GetZ(), m_currentSettings.m_worldBounds.GetMax().GetZ());
|
|
}
|
|
|
|
float TerrainSystem::GetTerrainAreaHeight(float x, float y, bool& terrainExists) const
|
|
{
|
|
const float worldMin = m_currentSettings.m_worldBounds.GetMin().GetZ();
|
|
AZ::Vector3 inPosition(x, y, worldMin);
|
|
float height = worldMin;
|
|
terrainExists = false;
|
|
|
|
AZStd::shared_lock<AZStd::shared_mutex> lock(m_areaMutex);
|
|
|
|
for (auto& [areaId, areaData] : m_registeredAreas)
|
|
{
|
|
const float areaMin = areaData.m_areaBounds.GetMin().GetZ();
|
|
inPosition.SetZ(areaMin);
|
|
if (areaData.m_areaBounds.Contains(inPosition))
|
|
{
|
|
AZ::Vector3 outPosition;
|
|
Terrain::TerrainAreaHeightRequestBus::Event(
|
|
areaId, &Terrain::TerrainAreaHeightRequestBus::Events::GetHeight, inPosition, outPosition, terrainExists);
|
|
height = outPosition.GetZ();
|
|
if (!terrainExists)
|
|
{
|
|
// If the terrain height provider doesn't have any data, then check the area's "use ground plane" setting.
|
|
// If it's set, then create a default ground plane by saying terrain exists at the minimum height for the area.
|
|
// Otherwise, we'll set the height at the terrain world minimum and say it doesn't exist.
|
|
terrainExists = areaData.m_useGroundPlane;
|
|
height = areaData.m_useGroundPlane ? areaMin : worldMin;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
float TerrainSystem::GetHeight(const AZ::Vector3& position, Sampler sampler, bool* terrainExistsPtr) const
|
|
{
|
|
return GetHeightSynchronous(position.GetX(), position.GetY(), sampler, terrainExistsPtr);
|
|
}
|
|
|
|
float TerrainSystem::GetHeightFromVector2(const AZ::Vector2& position, Sampler sampler, bool* terrainExistsPtr) const
|
|
{
|
|
return GetHeightSynchronous(position.GetX(), position.GetY(), sampler, terrainExistsPtr);
|
|
}
|
|
|
|
float TerrainSystem::GetHeightFromFloats(float x, float y, Sampler sampler, bool* terrainExistsPtr) const
|
|
{
|
|
return GetHeightSynchronous(x, y, sampler, terrainExistsPtr);
|
|
}
|
|
|
|
bool TerrainSystem::GetIsHole(const AZ::Vector3& position, Sampler sampler) const
|
|
{
|
|
bool terrainExists = false;
|
|
GetHeightSynchronous(position.GetX(), position.GetY(), sampler, &terrainExists);
|
|
return !terrainExists;
|
|
}
|
|
|
|
bool TerrainSystem::GetIsHoleFromVector2(const AZ::Vector2& position, Sampler sampler) const
|
|
{
|
|
bool terrainExists = false;
|
|
GetHeightSynchronous(position.GetX(), position.GetY(), sampler, &terrainExists);
|
|
return !terrainExists;
|
|
}
|
|
|
|
bool TerrainSystem::GetIsHoleFromFloats(float x, float y, Sampler sampler) const
|
|
{
|
|
bool terrainExists = false;
|
|
GetHeightSynchronous(x, y, sampler, &terrainExists);
|
|
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);
|
|
|
|
bool terrainExists = false;
|
|
|
|
AZ::Vector3 outNormal = AZ::Vector3::CreateAxisZ();
|
|
|
|
if (!InWorldBounds(x, y))
|
|
{
|
|
if (terrainExistsPtr)
|
|
{
|
|
*terrainExistsPtr = terrainExists;
|
|
return outNormal;
|
|
}
|
|
}
|
|
float range = m_currentSettings.m_heightQueryResolution / 2.0f;
|
|
const AZ::Vector2 left (x - range, y);
|
|
const AZ::Vector2 right(x + range, y);
|
|
const AZ::Vector2 up (x, y - range);
|
|
const AZ::Vector2 down (x, y + range);
|
|
|
|
AZ::Vector3 v1(up.GetX(), up.GetY(), GetHeightSynchronous(up.GetX(), up.GetY(), sampler, &terrainExists));
|
|
AZ::Vector3 v2(left.GetX(), left.GetY(), GetHeightSynchronous(left.GetX(), left.GetY(), sampler, &terrainExists));
|
|
AZ::Vector3 v3(right.GetX(), right.GetY(), GetHeightSynchronous(right.GetX(), right.GetY(), sampler, &terrainExists));
|
|
AZ::Vector3 v4(down.GetX(), down.GetY(), GetHeightSynchronous(down.GetX(), down.GetY(), sampler, &terrainExists));
|
|
|
|
outNormal = (v3 - v2).Cross(v4 - v1).GetNormalized();
|
|
|
|
if (terrainExistsPtr)
|
|
{
|
|
*terrainExistsPtr = terrainExists;
|
|
}
|
|
|
|
return outNormal;
|
|
}
|
|
|
|
AZ::Vector3 TerrainSystem::GetNormal(const AZ::Vector3& position, Sampler sampler, bool* terrainExistsPtr) const
|
|
{
|
|
return GetNormalSynchronous(position.GetX(), position.GetY(), sampler, terrainExistsPtr);
|
|
}
|
|
|
|
AZ::Vector3 TerrainSystem::GetNormalFromVector2(const AZ::Vector2& position, Sampler sampler, bool* terrainExistsPtr) const
|
|
{
|
|
return GetNormalSynchronous(position.GetX(), position.GetY(), sampler, terrainExistsPtr);
|
|
}
|
|
|
|
AZ::Vector3 TerrainSystem::GetNormalFromFloats(float x, float y, Sampler sampler, bool* terrainExistsPtr) const
|
|
{
|
|
return GetNormalSynchronous(x, y, sampler, terrainExistsPtr);
|
|
}
|
|
|
|
AzFramework::SurfaceData::SurfaceTagWeight TerrainSystem::GetMaxSurfaceWeight(
|
|
const AZ::Vector3& position, Sampler sampleFilter, bool* terrainExistsPtr) const
|
|
{
|
|
return GetMaxSurfaceWeightFromFloats(position.GetX(), position.GetY(), sampleFilter, terrainExistsPtr);
|
|
}
|
|
|
|
AzFramework::SurfaceData::SurfaceTagWeight TerrainSystem::GetMaxSurfaceWeightFromVector2(
|
|
const AZ::Vector2& inPosition, Sampler sampleFilter, bool* terrainExistsPtr) const
|
|
{
|
|
return GetMaxSurfaceWeightFromFloats(inPosition.GetX(), inPosition.GetY(), sampleFilter, terrainExistsPtr);
|
|
}
|
|
|
|
AzFramework::SurfaceData::SurfaceTagWeight TerrainSystem::GetMaxSurfaceWeightFromFloats(
|
|
const float x, const float y, Sampler sampleFilter, bool* terrainExistsPtr) const
|
|
{
|
|
if (terrainExistsPtr)
|
|
{
|
|
*terrainExistsPtr = true;
|
|
}
|
|
|
|
AzFramework::SurfaceData::SurfaceTagWeightList weightSet;
|
|
|
|
if (!InWorldBounds(x, y))
|
|
{
|
|
if (terrainExistsPtr)
|
|
{
|
|
*terrainExistsPtr = false;
|
|
return {};
|
|
}
|
|
}
|
|
|
|
GetOrderedSurfaceWeights(x, y, sampleFilter, weightSet, terrainExistsPtr);
|
|
|
|
if (weightSet.empty())
|
|
{
|
|
return {};
|
|
}
|
|
|
|
return *weightSet.begin();
|
|
}
|
|
|
|
void TerrainSystem::GetSurfacePoint(
|
|
const AZ::Vector3& inPosition,
|
|
AzFramework::SurfaceData::SurfacePoint& outSurfacePoint,
|
|
Sampler sampleFilter,
|
|
bool* terrainExistsPtr) const
|
|
{
|
|
outSurfacePoint.m_position = inPosition;
|
|
outSurfacePoint.m_position.SetZ(GetHeightSynchronous(inPosition.GetX(), inPosition.GetY(), sampleFilter, terrainExistsPtr));
|
|
outSurfacePoint.m_normal = GetNormalSynchronous(inPosition.GetX(), inPosition.GetY(), sampleFilter, nullptr);
|
|
GetSurfaceWeights(inPosition, outSurfacePoint.m_surfaceTags, sampleFilter, nullptr);
|
|
}
|
|
|
|
void TerrainSystem::GetSurfacePointFromVector2(
|
|
const AZ::Vector2& inPosition,
|
|
AzFramework::SurfaceData::SurfacePoint& outSurfacePoint,
|
|
Sampler sampleFilter,
|
|
bool* terrainExistsPtr) const
|
|
{
|
|
GetSurfacePoint(AZ::Vector3(inPosition.GetX(), inPosition.GetY(), 0.0f), outSurfacePoint, sampleFilter, terrainExistsPtr);
|
|
}
|
|
|
|
void TerrainSystem::GetSurfacePointFromFloats(
|
|
float x,
|
|
float y,
|
|
AzFramework::SurfaceData::SurfacePoint& outSurfacePoint,
|
|
Sampler sampleFilter,
|
|
bool* terrainExistsPtr) const
|
|
{
|
|
GetSurfacePoint(AZ::Vector3(x, y, 0.0f), outSurfacePoint, sampleFilter, terrainExistsPtr);
|
|
}
|
|
|
|
AzFramework::EntityContextId TerrainSystem::GetTerrainRaycastEntityContextId() const
|
|
{
|
|
return m_terrainRaycastContext.GetEntityContextId();
|
|
}
|
|
|
|
AzFramework::RenderGeometry::RayResult TerrainSystem::GetClosestIntersection(
|
|
const AzFramework::RenderGeometry::RayRequest& ray) const
|
|
{
|
|
return m_terrainRaycastContext.RayIntersect(ray);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessHeightsFromListAsync(
|
|
const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessHeightsFromList, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
|
inPositions, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessNormalsFromListAsync(
|
|
const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessNormalsFromList, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
|
inPositions, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfaceWeightsFromListAsync(
|
|
const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessSurfaceWeightsFromList, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
|
inPositions, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfacePointsFromListAsync(
|
|
const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessSurfacePointsFromList, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
|
inPositions, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessHeightsFromListOfVector2Async(
|
|
const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessHeightsFromListOfVector2, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
|
inPositions, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessNormalsFromListOfVector2Async(
|
|
const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessNormalsFromListOfVector2, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
|
inPositions, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfaceWeightsFromListOfVector2Async(
|
|
const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessSurfaceWeightsFromListOfVector2, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
|
inPositions, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfacePointsFromListOfVector2Async(
|
|
const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessSurfacePointsFromListOfVector2, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
|
inPositions, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessHeightsFromRegionAsync(
|
|
const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromRegionAsync(AZStd::bind(&TerrainSystem::ProcessHeightsFromRegion, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
|
|
inRegion, stepSize, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessNormalsFromRegionAsync(
|
|
const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromRegionAsync(AZStd::bind(&TerrainSystem::ProcessNormalsFromRegion, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
|
|
inRegion, stepSize, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfaceWeightsFromRegionAsync(
|
|
const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromRegionAsync(AZStd::bind(&TerrainSystem::ProcessSurfaceWeightsFromRegion, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
|
|
inRegion, stepSize, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfacePointsFromRegionAsync(
|
|
const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter,
|
|
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
|
{
|
|
return ProcessFromRegionAsync(AZStd::bind(&TerrainSystem::ProcessSurfacePointsFromRegion, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
|
|
inRegion, stepSize, perPositionCallback, sampleFilter, params);
|
|
}
|
|
|
|
AZ::EntityId TerrainSystem::FindBestAreaEntityAtPosition(float x, float y, AZ::Aabb& bounds) const
|
|
{
|
|
AZ::Vector3 inPosition = AZ::Vector3(x, y, 0);
|
|
|
|
// Find the highest priority layer that encompasses this position
|
|
AZStd::shared_lock<AZStd::shared_mutex> lock(m_areaMutex);
|
|
|
|
// The areas are sorted into priority order: the first area that contains inPosition is the most suitable.
|
|
for (const auto& [areaId, areaData] : m_registeredAreas)
|
|
{
|
|
inPosition.SetZ(areaData.m_areaBounds.GetMin().GetZ());
|
|
if (areaData.m_areaBounds.Contains(inPosition))
|
|
{
|
|
bounds = areaData.m_areaBounds;
|
|
return areaId;
|
|
}
|
|
}
|
|
|
|
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,
|
|
[[maybe_unused]] Sampler sampler,
|
|
AzFramework::SurfaceData::SurfaceTagWeightList& outSurfaceWeights,
|
|
bool* terrainExistsPtr) const
|
|
{
|
|
AZ::Aabb bounds;
|
|
AZ::EntityId bestAreaId = FindBestAreaEntityAtPosition(x, y, bounds);
|
|
|
|
if (terrainExistsPtr)
|
|
{
|
|
GetHeightFromFloats(x, y, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, terrainExistsPtr);
|
|
}
|
|
|
|
outSurfaceWeights.clear();
|
|
|
|
if (!bestAreaId.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const AZ::Vector3 inPosition = AZ::Vector3(x, y, 0.0f);
|
|
|
|
// Get all the surfaces with weights at the given point.
|
|
Terrain::TerrainAreaSurfaceRequestBus::Event(
|
|
bestAreaId, &Terrain::TerrainAreaSurfaceRequestBus::Events::GetSurfaceWeights, inPosition, outSurfaceWeights);
|
|
|
|
AZStd::sort(outSurfaceWeights.begin(), outSurfaceWeights.end(), AzFramework::SurfaceData::SurfaceTagWeightComparator());
|
|
}
|
|
|
|
void TerrainSystem::GetSurfaceWeights(
|
|
const AZ::Vector3& inPosition,
|
|
AzFramework::SurfaceData::SurfaceTagWeightList& outSurfaceWeights,
|
|
Sampler sampleFilter,
|
|
bool* terrainExistsPtr) const
|
|
{
|
|
GetOrderedSurfaceWeights(inPosition.GetX(), inPosition.GetY(), sampleFilter, outSurfaceWeights, terrainExistsPtr);
|
|
}
|
|
|
|
void TerrainSystem::GetSurfaceWeightsFromVector2(
|
|
const AZ::Vector2& inPosition,
|
|
AzFramework::SurfaceData::SurfaceTagWeightList& outSurfaceWeights,
|
|
Sampler sampleFilter,
|
|
bool* terrainExistsPtr) const
|
|
{
|
|
GetOrderedSurfaceWeights(inPosition.GetX(), inPosition.GetY(), sampleFilter, outSurfaceWeights, terrainExistsPtr);
|
|
}
|
|
|
|
void TerrainSystem::GetSurfaceWeightsFromFloats(
|
|
float x, float y,
|
|
AzFramework::SurfaceData::SurfaceTagWeightList& outSurfaceWeights,
|
|
Sampler sampleFilter,
|
|
bool* terrainExistsPtr) const
|
|
{
|
|
GetOrderedSurfaceWeights(x, y, sampleFilter, outSurfaceWeights, terrainExistsPtr);
|
|
}
|
|
|
|
const char* TerrainSystem::GetMaxSurfaceName(
|
|
[[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] Sampler sampleFilter, [[maybe_unused]] bool* terrainExistsPtr) const
|
|
{
|
|
// For now, always set terrainExists to true, as we don't have a way to author data for terrain holes yet.
|
|
if (terrainExistsPtr)
|
|
{
|
|
*terrainExistsPtr = true;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
void TerrainSystem::ProcessHeightsFromList(
|
|
const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
if (!perPositionCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZStd::vector<bool> terrainExists(inPositions.size());
|
|
AZStd::vector<float> heights(inPositions.size());
|
|
|
|
GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists);
|
|
|
|
AzFramework::SurfaceData::SurfacePoint surfacePoint;
|
|
for (size_t i = 0; i < inPositions.size(); i++)
|
|
{
|
|
surfacePoint.m_position = inPositions[i];
|
|
surfacePoint.m_position.SetZ(heights[i]);
|
|
perPositionCallback(surfacePoint, terrainExists[i]);
|
|
}
|
|
}
|
|
|
|
void TerrainSystem::ProcessNormalsFromList(
|
|
const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
if (!perPositionCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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 i = 0; i < inPositions.size(); i++)
|
|
{
|
|
surfacePoint.m_position = inPositions[i];
|
|
surfacePoint.m_normal = AZStd::move(normals[i]);
|
|
perPositionCallback(surfacePoint, terrainExists[i]);
|
|
}
|
|
}
|
|
|
|
void TerrainSystem::ProcessSurfaceWeightsFromList(
|
|
const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
if (!perPositionCallback)
|
|
{
|
|
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 (size_t i = 0; i < inPositions.size(); i++)
|
|
{
|
|
surfacePoint.m_position = inPositions[i];
|
|
surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]);
|
|
perPositionCallback(surfacePoint, terrainExists[i]);
|
|
}
|
|
}
|
|
|
|
void TerrainSystem::ProcessSurfacePointsFromList(
|
|
const AZStd::span<AZ::Vector3>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
if (!perPositionCallback)
|
|
{
|
|
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 (size_t i = 0; i < inPositions.size(); i++)
|
|
{
|
|
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]);
|
|
}
|
|
}
|
|
|
|
void TerrainSystem::ProcessHeightsFromListOfVector2(
|
|
const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
if (!perPositionCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AzFramework::SurfaceData::SurfacePoint surfacePoint;
|
|
for (const auto& position : inPositions)
|
|
{
|
|
bool terrainExists = false;
|
|
surfacePoint.m_position.Set(position.GetX(), position.GetY(), 0.0f);
|
|
surfacePoint.m_position.SetZ(GetHeightFromVector2(position, sampleFilter, &terrainExists));
|
|
perPositionCallback(surfacePoint, terrainExists);
|
|
}
|
|
}
|
|
|
|
void TerrainSystem::ProcessNormalsFromListOfVector2(
|
|
const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
if (!perPositionCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AzFramework::SurfaceData::SurfacePoint surfacePoint;
|
|
for (const auto& position : inPositions)
|
|
{
|
|
bool terrainExists = false;
|
|
surfacePoint.m_position.Set(position.GetX(), position.GetY(), 0.0f);
|
|
surfacePoint.m_normal = GetNormalFromVector2(position, sampleFilter, &terrainExists);
|
|
perPositionCallback(surfacePoint, terrainExists);
|
|
}
|
|
}
|
|
|
|
void TerrainSystem::ProcessSurfaceWeightsFromListOfVector2(
|
|
const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
if (!perPositionCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AzFramework::SurfaceData::SurfacePoint surfacePoint;
|
|
for (const auto& position : inPositions)
|
|
{
|
|
bool terrainExists = false;
|
|
surfacePoint.m_position.Set(position.GetX(), position.GetY(), 0.0f);
|
|
GetSurfaceWeightsFromVector2(position, surfacePoint.m_surfaceTags, sampleFilter, &terrainExists);
|
|
perPositionCallback(surfacePoint, terrainExists);
|
|
}
|
|
}
|
|
|
|
void TerrainSystem::ProcessSurfacePointsFromListOfVector2(
|
|
const AZStd::span<AZ::Vector2>& inPositions,
|
|
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
if (!perPositionCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AzFramework::SurfaceData::SurfacePoint surfacePoint;
|
|
for (const auto& position : inPositions)
|
|
{
|
|
bool terrainExists = false;
|
|
surfacePoint.m_position.Set(position.GetX(), position.GetY(), 0.0f);
|
|
GetSurfacePointFromVector2(position, surfacePoint, sampleFilter, &terrainExists);
|
|
perPositionCallback(surfacePoint, terrainExists);
|
|
}
|
|
}
|
|
|
|
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,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
// Don't bother processing if we don't have a callback
|
|
if (!perPositionCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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, i = 0; y < numSamplesY; y++)
|
|
{
|
|
for (size_t x = 0; x < numSamplesX; x++)
|
|
{
|
|
surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), heights[i]);
|
|
perPositionCallback(x, y, surfacePoint, terrainExists[i]);
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerrainSystem::ProcessNormalsFromRegion(
|
|
const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
// Don't bother processing if we don't have a callback
|
|
if (!perPositionCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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, i = 0; y < numSamplesY; y++)
|
|
{
|
|
for (size_t x = 0; x < numSamplesX; x++)
|
|
{
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerrainSystem::ProcessSurfaceWeightsFromRegion(
|
|
const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
// Don't bother processing if we don't have a callback
|
|
if (!perPositionCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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, i = 0; y < numSamplesY; y++)
|
|
{
|
|
for (size_t x = 0; x < numSamplesX; x++)
|
|
{
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerrainSystem::ProcessSurfacePointsFromRegion(
|
|
const AZ::Aabb& inRegion,
|
|
const AZ::Vector2& stepSize,
|
|
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
|
Sampler sampleFilter) const
|
|
{
|
|
// Don't bother processing if we don't have a callback
|
|
if (!perPositionCallback)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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, i = 0; y < numSamplesY; y++)
|
|
{
|
|
for (size_t x = 0; x < numSamplesX; x++)
|
|
{
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerrainSystem::RegisterArea(AZ::EntityId areaId)
|
|
{
|
|
AZStd::unique_lock<AZStd::shared_mutex> lock(m_areaMutex);
|
|
AZ::Aabb aabb = AZ::Aabb::CreateNull();
|
|
LmbrCentral::ShapeComponentRequestsBus::EventResult(aabb, areaId, &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
|
|
|
|
// Cache off whether or not this layer spawner should have a default ground plane when no other terrain height data exists.
|
|
bool useGroundPlane = false;
|
|
Terrain::TerrainSpawnerRequestBus::EventResult(useGroundPlane, areaId, &Terrain::TerrainSpawnerRequestBus::Events::GetUseGroundPlane);
|
|
|
|
m_registeredAreas[areaId] = { aabb, useGroundPlane };
|
|
m_dirtyRegion.AddAabb(aabb);
|
|
m_terrainHeightDirty = true;
|
|
m_terrainSurfacesDirty = true;
|
|
}
|
|
|
|
void TerrainSystem::UnregisterArea(AZ::EntityId areaId)
|
|
{
|
|
AZStd::unique_lock<AZStd::shared_mutex> lock(m_areaMutex);
|
|
|
|
// Remove the data for this entity from the registered areas.
|
|
// Erase_if is used as erase would use the comparator to lookup the entity id in the map.
|
|
// As the comparator will get the new layer/priority data for the entity, the id lookup will fail.
|
|
AZStd::erase_if(
|
|
m_registeredAreas,
|
|
[areaId, this](const auto& item)
|
|
{
|
|
auto const& [entityId, areaData] = item;
|
|
if (areaId == entityId)
|
|
{
|
|
m_dirtyRegion.AddAabb(areaData.m_areaBounds);
|
|
m_terrainHeightDirty = true;
|
|
m_terrainSurfacesDirty = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
void TerrainSystem::RefreshArea(AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask)
|
|
{
|
|
using Terrain = AzFramework::Terrain::TerrainDataNotifications;
|
|
|
|
AZStd::unique_lock<AZStd::shared_mutex> lock(m_areaMutex);
|
|
|
|
auto areaAabb = m_registeredAreas.find(areaId);
|
|
|
|
AZ::Aabb oldAabb = (areaAabb != m_registeredAreas.end()) ? areaAabb->second.m_areaBounds : AZ::Aabb::CreateNull();
|
|
AZ::Aabb newAabb = AZ::Aabb::CreateNull();
|
|
LmbrCentral::ShapeComponentRequestsBus::EventResult(newAabb, areaId, &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
|
|
m_registeredAreas[areaId].m_areaBounds = newAabb;
|
|
|
|
AZ::Aabb expandedAabb = oldAabb;
|
|
expandedAabb.AddAabb(newAabb);
|
|
|
|
m_dirtyRegion.AddAabb(expandedAabb);
|
|
|
|
// Keep track of which types of data have changed so that we can send out the appropriate notifications later.
|
|
|
|
m_terrainHeightDirty = m_terrainHeightDirty || ((changeMask & Terrain::HeightData) == Terrain::HeightData);
|
|
|
|
m_terrainSurfacesDirty = m_terrainSurfacesDirty || ((changeMask & Terrain::SurfaceData) == Terrain::SurfaceData);
|
|
}
|
|
|
|
void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
|
|
{
|
|
using Terrain = AzFramework::Terrain::TerrainDataNotifications;
|
|
|
|
bool terrainSettingsChanged = false;
|
|
|
|
if (m_terrainSettingsDirty)
|
|
{
|
|
terrainSettingsChanged = true;
|
|
m_terrainSettingsDirty = false;
|
|
|
|
// This needs to happen before the "system active" check below, because activating the system will cause the various
|
|
// terrain layer areas to request the current world bounds.
|
|
if (m_requestedSettings.m_worldBounds != m_currentSettings.m_worldBounds)
|
|
{
|
|
m_dirtyRegion = m_currentSettings.m_worldBounds;
|
|
m_dirtyRegion.AddAabb(m_requestedSettings.m_worldBounds);
|
|
m_terrainHeightDirty = true;
|
|
m_terrainSurfacesDirty = true;
|
|
m_currentSettings.m_worldBounds = m_requestedSettings.m_worldBounds;
|
|
}
|
|
|
|
if (m_requestedSettings.m_heightQueryResolution != m_currentSettings.m_heightQueryResolution)
|
|
{
|
|
m_dirtyRegion = AZ::Aabb::CreateNull();
|
|
m_terrainHeightDirty = true;
|
|
m_terrainSurfacesDirty = true;
|
|
}
|
|
|
|
m_currentSettings = m_requestedSettings;
|
|
}
|
|
|
|
if (terrainSettingsChanged || m_terrainHeightDirty || m_terrainSurfacesDirty)
|
|
{
|
|
// Block other threads from accessing the surface data bus while we are in GetValue (which may call into the SurfaceData bus).
|
|
// We lock our surface data mutex *before* checking / setting "isRequestInProgress" so that we prevent race conditions
|
|
// that create false detection of cyclic dependencies when multiple requests occur on different threads simultaneously.
|
|
// (One case where this was previously able to occur was in rapid updating of the Preview widget on the
|
|
// GradientSurfaceDataComponent in the Editor when moving the threshold sliders back and forth rapidly)
|
|
auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false);
|
|
typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex);
|
|
|
|
Terrain::TerrainDataChangedMask changeMask = Terrain::TerrainDataChangedMask::None;
|
|
|
|
if (terrainSettingsChanged)
|
|
{
|
|
changeMask = static_cast<Terrain::TerrainDataChangedMask>(changeMask | Terrain::TerrainDataChangedMask::Settings);
|
|
}
|
|
if (m_terrainHeightDirty)
|
|
{
|
|
changeMask = static_cast<Terrain::TerrainDataChangedMask>(changeMask | Terrain::TerrainDataChangedMask::HeightData);
|
|
}
|
|
|
|
if (m_terrainSurfacesDirty)
|
|
{
|
|
changeMask = static_cast<Terrain::TerrainDataChangedMask>(changeMask | Terrain::TerrainDataChangedMask::SurfaceData);
|
|
}
|
|
|
|
// Make sure to set these *before* calling OnTerrainDataChanged, since it's possible that subsystems reacting to that call will
|
|
// cause the data to become dirty again.
|
|
AZ::Aabb dirtyRegion = m_dirtyRegion;
|
|
m_terrainHeightDirty = false;
|
|
m_terrainSurfacesDirty = false;
|
|
m_dirtyRegion = AZ::Aabb::CreateNull();
|
|
|
|
AzFramework::Terrain::TerrainDataNotificationBus::Broadcast(
|
|
&AzFramework::Terrain::TerrainDataNotificationBus::Events::OnTerrainDataChanged, dirtyRegion,
|
|
changeMask);
|
|
}
|
|
|
|
}
|