merge stabilization/2110 to development - 2021/11/09

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>
This commit is contained in:
Tom Hulton-Harrop
2021-11-09 09:19:54 +00:00
89 changed files with 1205 additions and 1125 deletions
@@ -151,24 +151,32 @@ namespace Terrain
{
float maxSample = 0.0f;
terrainExists = false;
GradientSignal::GradientSampleParams params(AZ::Vector3(inPosition.GetX(), inPosition.GetY(), 0.0f));
// Right now, when the list contains multiple entries, we will use the highest point from each gradient.
// This is needed in part because gradients don't really have world bounds, so they exist everywhere but generally have a value
// of 0 outside their data bounds if they're using bounded data. We should examine the possibility of extending the gradient API
// to provide actual bounds so that it's possible to detect if the gradient even 'exists' in an area, at which point we could just
// make this list a prioritized list from top to bottom for any points that overlap.
for (auto& gradientId : m_configuration.m_gradientEntities)
AZ_WarningOnce("Terrain", !m_isRequestInProgress, "Detected cyclic dependences with terrain height entity references");
if (!m_isRequestInProgress)
{
// If gradients ever provide bounds, or if we add a value threshold in this component, it would be possible for terrain
// to *not* exist at a specific point.
terrainExists = true;
m_isRequestInProgress = true;
GradientSignal::GradientSampleParams params(AZ::Vector3(inPosition.GetX(), inPosition.GetY(), 0.0f));
float sample = 0.0f;
GradientSignal::GradientRequestBus::EventResult(
sample, gradientId, &GradientSignal::GradientRequestBus::Events::GetValue, params);
maxSample = AZ::GetMax(maxSample, sample);
// Right now, when the list contains multiple entries, we will use the highest point from each gradient.
// This is needed in part because gradients don't really have world bounds, so they exist everywhere but generally have a value
// of 0 outside their data bounds if they're using bounded data. We should examine the possibility of extending the gradient
// API to provide actual bounds so that it's possible to detect if the gradient even 'exists' in an area, at which point we
// could just make this list a prioritized list from top to bottom for any points that overlap.
for (auto& gradientId : m_configuration.m_gradientEntities)
{
if (gradientId.IsValid())
{
// If gradients ever provide bounds, or if we add a value threshold in this component, it would be possible for terrain
// to *not* exist at a specific point.
terrainExists = true;
float sample = 0.0f;
GradientSignal::GradientRequestBus::EventResult(
sample, gradientId, &GradientSignal::GradientRequestBus::Events::GetValue, params);
maxSample = AZ::GetMax(maxSample, sample);
}
}
m_isRequestInProgress = false;
}
const float height = AZ::Lerp(m_cachedShapeBounds.GetMin().GetZ(), m_cachedShapeBounds.GetMax().GetZ(), maxSample);
@@ -91,6 +91,9 @@ namespace Terrain
AZ::Vector2 m_cachedHeightQueryResolution{ 1.0f, 1.0f };
AZ::Aabb m_cachedShapeBounds;
// prevent recursion in case user attaches cyclic dependences
mutable bool m_isRequestInProgress{ false };
LmbrCentral::DependencyMonitor m_dependencyMonitor;
};
}
@@ -218,6 +218,12 @@ namespace Terrain
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution);
// Take the dirty region and adjust the Z values to the world min/max so that even if the dirty region falls outside the current
// world bounds, we still update the wireframe accordingly.
AZ::Aabb dirtyRegion2D = AZ::Aabb::CreateFromMinMaxValues(
dirtyRegion.GetMin().GetX(), dirtyRegion.GetMin().GetY(), worldBounds.GetMin().GetZ(),
dirtyRegion.GetMax().GetX(), dirtyRegion.GetMax().GetY(), worldBounds.GetMax().GetZ());
// Calculate the world size of each sector. Note that this size actually ends at the last point, not the last square.
// So for example, the sector size for 3 points will go from (*--*--*) even though it will be used to draw (*--*--*--).
const float xSectorSize = (queryResolution.GetX() * SectorSizeInGridPoints);
@@ -230,7 +236,7 @@ namespace Terrain
// If we haven't cached anything before, or if the world bounds has changed, clear our cache structure and repopulate it
// with WireframeSector entries with the proper AABB sizes.
if (!m_wireframeBounds.IsValid() || !dirtyRegion.IsValid() || !m_wireframeBounds.IsClose(worldBounds))
if (!m_wireframeBounds.IsValid() || !dirtyRegion2D.IsValid() || !m_wireframeBounds.IsClose(worldBounds))
{
m_wireframeBounds = worldBounds;
@@ -266,7 +272,7 @@ namespace Terrain
// For each sector, if it overlaps with the dirty region, clear it out and recache the wireframe line data.
for (auto& sector : m_wireframeSectors)
{
if (dirtyRegion.IsValid() && !dirtyRegion.Overlaps(sector.m_aabb))
if (dirtyRegion2D.IsValid() && !dirtyRegion2D.Overlaps(sector.m_aabb))
{
continue;
}
@@ -46,6 +46,7 @@ namespace Terrain
->EnumAttribute(TerrainWorldRendererConfig::WorldSize::_4096Meters, "4 Kilometers")
->EnumAttribute(TerrainWorldRendererConfig::WorldSize::_8192Meters, "8 Kilometers")
->EnumAttribute(TerrainWorldRendererConfig::WorldSize::_16384Meters, "16 Kilometers")
->Attribute(AZ::Edit::Attributes::Visibility, false) // Keeping invisible until it's hooked up under the hood
;
}
}
@@ -972,10 +972,10 @@ namespace Terrain
m_areaData.m_rebuildSectors = false;
m_sectorData.clear();
const float xFirstPatchStart = terrainBounds.GetMin().GetX() - fmod(terrainBounds.GetMin().GetX(), GridMeters);
const float xLastPatchStart = terrainBounds.GetMax().GetX() - fmod(terrainBounds.GetMax().GetX(), GridMeters);
const float yFirstPatchStart = terrainBounds.GetMin().GetY() - fmod(terrainBounds.GetMin().GetY(), GridMeters);
const float yLastPatchStart = terrainBounds.GetMax().GetY() - fmod(terrainBounds.GetMax().GetY(), GridMeters);
const float xFirstPatchStart = AZStd::floorf(terrainBounds.GetMin().GetX() / GridMeters) * GridMeters;
const float xLastPatchStart = AZStd::floorf(terrainBounds.GetMax().GetX() / GridMeters) * GridMeters;
const float yFirstPatchStart = AZStd::floorf(terrainBounds.GetMin().GetY() / GridMeters) * GridMeters;
const float yLastPatchStart = AZStd::floorf(terrainBounds.GetMax().GetY() / GridMeters) * GridMeters;
const auto& materialAsset = m_materialInstance->GetAsset();
const auto& shaderAsset = materialAsset->GetMaterialTypeAsset()->GetShaderAssetForObjectSrg();
@@ -1217,7 +1217,7 @@ namespace Terrain
// For every distance doubling beyond a minDistanceForLod0, we only need half the mesh density. Each LOD
// is exactly half the resolution of the last.
const float lodForCamera = floorf(AZ::GetMax(0.0f, log2f(sectorDistance / minDistanceForLod0)));
const float lodForCamera = AZStd::floorf(AZ::GetMax(0.0f, log2f(sectorDistance / minDistanceForLod0)));
// All cameras should render the same LOD so effects like shadows are consistent.
lodChoice = AZ::GetMin(lodChoice, aznumeric_cast<uint8_t>(lodForCamera));
@@ -222,20 +222,31 @@ float TerrainSystem::GetHeightSynchronous(float x, float y, Sampler sampler, boo
float TerrainSystem::GetTerrainAreaHeight(float x, float y, bool& terrainExists) const
{
AZ::Vector3 inPosition((float)x, (float)y, m_currentSettings.m_worldBounds.GetMin().GetZ());
float height = m_currentSettings.m_worldBounds.GetMin().GetZ();
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, areaBounds] : m_registeredAreas)
for (auto& [areaId, areaData] : m_registeredAreas)
{
inPosition.SetZ(areaBounds.GetMin().GetZ());
if (areaBounds.Contains(inPosition))
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;
}
}
@@ -395,12 +406,12 @@ AZ::EntityId TerrainSystem::FindBestAreaEntityAtPosition(float x, float y, AZ::A
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, areaBounds] : m_registeredAreas)
for (const auto& [areaId, areaData] : m_registeredAreas)
{
inPosition.SetZ(areaBounds.GetMin().GetZ());
if (areaBounds.Contains(inPosition))
inPosition.SetZ(areaData.m_areaBounds.GetMin().GetZ());
if (areaData.m_areaBounds.Contains(inPosition))
{
bounds = areaBounds;
bounds = areaData.m_areaBounds;
return areaId;
}
}
@@ -548,7 +559,12 @@ 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);
m_registeredAreas[areaId] = aabb;
// 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;
@@ -565,10 +581,10 @@ void TerrainSystem::UnregisterArea(AZ::EntityId areaId)
m_registeredAreas,
[areaId, this](const auto& item)
{
auto const& [entityId, aabb] = item;
auto const& [entityId, areaData] = item;
if (areaId == entityId)
{
m_dirtyRegion.AddAabb(aabb);
m_dirtyRegion.AddAabb(areaData.m_areaBounds);
m_terrainHeightDirty = true;
m_terrainSurfacesDirty = true;
return true;
@@ -585,10 +601,10 @@ void TerrainSystem::RefreshArea(AZ::EntityId areaId, AzFramework::Terrain::Terra
auto areaAabb = m_registeredAreas.find(areaId);
AZ::Aabb oldAabb = (areaAabb != m_registeredAreas.end()) ? areaAabb->second : AZ::Aabb::CreateNull();
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] = newAabb;
m_registeredAreas[areaId].m_areaBounds = newAabb;
AZ::Aabb expandedAabb = oldAabb;
expandedAabb.AddAabb(newAabb);
@@ -168,7 +168,14 @@ namespace Terrain
bool m_terrainSurfacesDirty = false;
AZ::Aabb m_dirtyRegion;
// Cached data for each terrain area to use when looking up terrain data.
struct TerrainAreaData
{
AZ::Aabb m_areaBounds{ AZ::Aabb::CreateNull() };
bool m_useGroundPlane{ false };
};
mutable AZStd::shared_mutex m_areaMutex;
AZStd::map<AZ::EntityId, AZ::Aabb, TerrainLayerPriorityComparator> m_registeredAreas;
AZStd::map<AZ::EntityId, TerrainAreaData, TerrainLayerPriorityComparator> m_registeredAreas;
};
} // namespace Terrain