2f1346c719
* Breaking up terrain FP wip - macro materials decoupled from meshes. Mesh creation and rendering pulled out from MeshFeatureProcessor into TerrainMeshManager. Stubs added for macro and detail material managers. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Separated macro material management from the terrain feature processor. Also separated bindless image array handling from terrain feature processor. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Detail materials separated from terrain feature processor. Also pulled out Aabb2i and Vector2i into their own simple classes Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Changed some classes so that when the SRG changes the classes don't need to be completely reinitialized and can instead just update their indices and push data to the new srg. Fixed an issue where MacroMaterialData wasn't being exposed to ScriptCanvas. Terrain shader reloads should now work correctly. Also added some debug logging to help catch an issue where sometimes detail materials don't seem to load. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Terrain PR reveiw updates Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Some small PR review fixes. More comments in TerrainDetailMaterialManager.h Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Fixing unused variable causing clang failure Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Fixing unused variable in release. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Fixing a forward declare that oddly didn't work on linux. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Fixing linux missing include... not sure why only linux failed on this. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Adding missing include Signed-off-by: Ken Pruiksma <pruiksma@amazon.com>
913 lines
44 KiB
C++
913 lines
44 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 <TerrainRenderer/TerrainDetailMaterialManager.h>
|
|
#include <TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h>
|
|
|
|
#include <AzCore/Console/Console.h>
|
|
|
|
#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
|
|
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
|
|
#include <Atom/RPI.Public/Shader/ShaderSystemInterface.h>
|
|
|
|
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
|
|
|
|
namespace Terrain
|
|
{
|
|
namespace
|
|
{
|
|
[[maybe_unused]] static const char* TerrainDetailMaterialManagerName = "TerrainDetailMaterialManager";
|
|
static const char* TerrainDetailChars = "TerrainDetail";
|
|
}
|
|
|
|
namespace DetailMaterialInputs
|
|
{
|
|
static const char* const BaseColorColor("baseColor.color");
|
|
static const char* const BaseColorMap("baseColor.textureMap");
|
|
static const char* const BaseColorUseTexture("baseColor.useTexture");
|
|
static const char* const BaseColorFactor("baseColor.factor");
|
|
static const char* const BaseColorBlendMode("baseColor.textureBlendMode");
|
|
static const char* const MetallicMap("metallic.textureMap");
|
|
static const char* const MetallicUseTexture("metallic.useTexture");
|
|
static const char* const MetallicFactor("metallic.factor");
|
|
static const char* const RoughnessMap("roughness.textureMap");
|
|
static const char* const RoughnessUseTexture("roughness.useTexture");
|
|
static const char* const RoughnessFactor("roughness.factor");
|
|
static const char* const RoughnessLowerBound("roughness.lowerBound");
|
|
static const char* const RoughnessUpperBound("roughness.upperBound");
|
|
static const char* const SpecularF0Map("specularF0.textureMap");
|
|
static const char* const SpecularF0UseTexture("specularF0.useTexture");
|
|
static const char* const SpecularF0Factor("specularF0.factor");
|
|
static const char* const NormalMap("normal.textureMap");
|
|
static const char* const NormalUseTexture("normal.useTexture");
|
|
static const char* const NormalFactor("normal.factor");
|
|
static const char* const NormalFlipX("normal.flipX");
|
|
static const char* const NormalFlipY("normal.flipY");
|
|
static const char* const DiffuseOcclusionMap("occlusion.diffuseTextureMap");
|
|
static const char* const DiffuseOcclusionUseTexture("occlusion.diffuseUseTexture");
|
|
static const char* const DiffuseOcclusionFactor("occlusion.diffuseFactor");
|
|
static const char* const HeightMap("parallax.textureMap");
|
|
static const char* const HeightUseTexture("parallax.useTexture");
|
|
static const char* const HeightFactor("parallax.factor");
|
|
static const char* const HeightOffset("parallax.offset");
|
|
static const char* const HeightBlendFactor("parallax.blendFactor");
|
|
}
|
|
|
|
namespace TerrainSrgInputs
|
|
{
|
|
static const char* const DetailMaterialIdImage("m_detailMaterialIdImage");
|
|
static const char* const DetailMaterialData("m_detailMaterialData");
|
|
static const char* const DetailMaterialIdImageCenter("m_detailMaterialIdImageCenter");
|
|
static const char* const DetailHalfPixelUv("m_detailHalfPixelUv");
|
|
static const char* const DetailAabb("m_detailAabb");
|
|
}
|
|
|
|
AZ_CVAR(bool,
|
|
r_terrainDebugDetailMaterials,
|
|
false,
|
|
[](const bool& value)
|
|
{
|
|
AZ::RPI::ShaderSystemInterface::Get()->SetGlobalShaderOption(AZ::Name{ "o_debugDetailMaterialIds" }, AZ::RPI::ShaderOptionValue{ value });
|
|
},
|
|
AZ::ConsoleFunctorFlags::Null,
|
|
"Turns on debugging for detail material ids for terrain."
|
|
);
|
|
|
|
AZ_CVAR(bool,
|
|
r_terrainDebugDetailImageUpdates,
|
|
false,
|
|
nullptr,
|
|
AZ::ConsoleFunctorFlags::Null,
|
|
"Turns on debugging for detail material update regions for terrain."
|
|
);
|
|
|
|
void TerrainDetailMaterialManager::Initialize(
|
|
const AZStd::shared_ptr<AZ::Render::BindlessImageArrayHandler>& bindlessImageHandler,
|
|
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
|
|
{
|
|
AZ_Error(TerrainDetailMaterialManagerName, bindlessImageHandler, "bindlessImageHandler must not be null.");
|
|
AZ_Error(TerrainDetailMaterialManagerName, terrainSrg, "terrainSrg must not be null.");
|
|
AZ_Error(TerrainDetailMaterialManagerName, !m_isInitialized, "Already initialized.");
|
|
|
|
if (!bindlessImageHandler || !terrainSrg || m_isInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (UpdateSrgIndices(terrainSrg))
|
|
{
|
|
m_bindlessImageHandler = bindlessImageHandler;
|
|
|
|
// Find any detail material areas that have already been created.
|
|
TerrainAreaMaterialRequestBus::EnumerateHandlers(
|
|
[&](TerrainAreaMaterialRequests* handler)
|
|
{
|
|
const AZ::Aabb& bounds = handler->GetTerrainSurfaceMaterialRegion();
|
|
const AZStd::vector<TerrainSurfaceMaterialMapping> materialMappings = handler->GetSurfaceMaterialMappings();
|
|
AZ::EntityId entityId = *(Terrain::TerrainAreaMaterialRequestBus::GetCurrentBusId());
|
|
|
|
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
|
|
materialRegion.m_region = bounds;
|
|
|
|
for (const auto& materialMapping : materialMappings)
|
|
{
|
|
if (materialMapping.m_materialInstance)
|
|
{
|
|
OnTerrainSurfaceMaterialMappingCreated(entityId, materialMapping.m_surfaceTag, materialMapping.m_materialInstance);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
);
|
|
TerrainAreaMaterialNotificationBus::Handler::BusConnect();
|
|
|
|
AZ::Aabb worldBounds = AZ::Aabb::CreateNull();
|
|
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
|
|
worldBounds, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
|
|
|
|
OnTerrainDataChanged(worldBounds, TerrainDataChangedMask::SurfaceData);
|
|
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect();
|
|
|
|
m_isInitialized = true;
|
|
}
|
|
}
|
|
|
|
bool TerrainDetailMaterialManager::UpdateSrgIndices(AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
|
|
{
|
|
const AZ::RHI::ShaderResourceGroupLayout* terrainSrgLayout = terrainSrg->GetLayout();
|
|
|
|
m_detailMaterialIdPropertyIndex = terrainSrgLayout->FindShaderInputImageIndex(AZ::Name(TerrainSrgInputs::DetailMaterialIdImage));
|
|
AZ_Error(TerrainDetailMaterialManagerName, m_detailMaterialIdPropertyIndex.IsValid(), "Failed to find terrain srg input constant %s.", TerrainSrgInputs::DetailMaterialIdImage);
|
|
|
|
m_detailCenterPropertyIndex = terrainSrgLayout->FindShaderInputConstantIndex(AZ::Name(TerrainSrgInputs::DetailMaterialIdImageCenter));
|
|
AZ_Error(TerrainDetailMaterialManagerName, m_detailCenterPropertyIndex.IsValid(), "Failed to find terrain srg input constant %s.", TerrainSrgInputs::DetailMaterialIdImageCenter);
|
|
|
|
m_detailHalfPixelUvPropertyIndex = terrainSrgLayout->FindShaderInputConstantIndex(AZ::Name(TerrainSrgInputs::DetailHalfPixelUv));
|
|
AZ_Error(TerrainDetailMaterialManagerName, m_detailHalfPixelUvPropertyIndex.IsValid(), "Failed to find terrain srg input constant %s.", TerrainSrgInputs::DetailHalfPixelUv);
|
|
|
|
m_detailAabbPropertyIndex = terrainSrgLayout->FindShaderInputConstantIndex(AZ::Name(TerrainSrgInputs::DetailAabb));
|
|
AZ_Error(TerrainDetailMaterialManagerName, m_detailAabbPropertyIndex.IsValid(), "Failed to find terrain srg input constant %s.", TerrainSrgInputs::DetailAabb);
|
|
|
|
// Set up the gpu buffer for detail material data
|
|
AZ::Render::GpuBufferHandler::Descriptor desc;
|
|
desc.m_bufferName = "Detail Material Data";
|
|
desc.m_bufferSrgName = TerrainSrgInputs::DetailMaterialData;
|
|
desc.m_elementSize = sizeof(DetailMaterialShaderData);
|
|
desc.m_srgLayout = terrainSrgLayout;
|
|
m_detailMaterialDataBuffer = AZ::Render::GpuBufferHandler(desc);
|
|
|
|
bool IndicesValid =
|
|
m_detailMaterialIdPropertyIndex.IsValid() &&
|
|
m_detailCenterPropertyIndex.IsValid() &&
|
|
m_detailHalfPixelUvPropertyIndex.IsValid() &&
|
|
m_detailAabbPropertyIndex.IsValid();
|
|
|
|
m_detailImageNeedsUpdate = true;
|
|
m_detailMaterialBufferNeedsUpdate = true;
|
|
|
|
return IndicesValid && m_detailMaterialDataBuffer.IsValid();
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::RemoveAllImages()
|
|
{
|
|
for (const DetailMaterialData& materialData: m_detailMaterials.GetDataVector())
|
|
{
|
|
DetailMaterialShaderData& shaderData = m_detailMaterialShaderData.GetElement(materialData.m_detailMaterialBufferIndex);
|
|
|
|
auto checkRemoveImage = [&](uint16_t index)
|
|
{
|
|
if (index != 0xFFFF)
|
|
{
|
|
m_bindlessImageHandler->RemoveBindlessImage(index);
|
|
}
|
|
};
|
|
|
|
checkRemoveImage(shaderData.m_colorImageIndex);
|
|
checkRemoveImage(shaderData.m_normalImageIndex);
|
|
checkRemoveImage(shaderData.m_roughnessImageIndex);
|
|
checkRemoveImage(shaderData.m_metalnessImageIndex);
|
|
checkRemoveImage(shaderData.m_specularF0ImageIndex);
|
|
checkRemoveImage(shaderData.m_occlusionImageIndex);
|
|
checkRemoveImage(shaderData.m_heightImageIndex);
|
|
}
|
|
}
|
|
|
|
bool TerrainDetailMaterialManager::IsInitialized() const
|
|
{
|
|
return m_isInitialized;
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::Reset()
|
|
{
|
|
RemoveAllImages();
|
|
m_bindlessImageHandler.reset();
|
|
|
|
m_detailTextureImage = {};
|
|
m_detailMaterials.Clear();
|
|
m_detailMaterialRegions.Clear();
|
|
m_detailMaterialShaderData.Clear();
|
|
m_detailMaterialDataBuffer.Release();
|
|
|
|
m_dirtyDetailRegion = AZ::Aabb::CreateNull();
|
|
m_previousCameraPosition = AZ::Vector3(AZStd::numeric_limits<float>::max(), 0.0, 0.0);
|
|
m_detailTextureBounds = {};
|
|
m_detailTextureCenter = {};
|
|
|
|
m_detailMaterialBufferNeedsUpdate = false;
|
|
m_detailImageNeedsUpdate = false;
|
|
|
|
TerrainAreaMaterialNotificationBus::Handler::BusDisconnect();
|
|
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect();
|
|
|
|
m_isInitialized = false;
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::Update(const AZ::Vector3& cameraPosition, AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
|
|
{
|
|
if (m_detailMaterialBufferNeedsUpdate)
|
|
{
|
|
m_detailMaterialBufferNeedsUpdate = false;
|
|
m_detailMaterialDataBuffer.UpdateBuffer(m_detailMaterialShaderData.GetRawData(), aznumeric_cast<uint32_t>(m_detailMaterialShaderData.GetSize()));
|
|
}
|
|
|
|
if (m_dirtyDetailRegion.IsValid() || !cameraPosition.IsClose(m_previousCameraPosition) || m_detailImageNeedsUpdate)
|
|
{
|
|
if (r_terrainDebugDetailImageUpdates)
|
|
{
|
|
AZ_Printf("TerrainDetailMaterialManager", "Previous Camera: (%f, %f, %f) New Cameara: (%f, %f, %f)",
|
|
m_previousCameraPosition.GetX(), m_previousCameraPosition.GetY(), m_previousCameraPosition.GetZ(),
|
|
cameraPosition.GetX(), cameraPosition.GetY(), cameraPosition.GetZ());
|
|
}
|
|
int32_t newDetailTexturePosX = aznumeric_cast<int32_t>(AZStd::roundf(cameraPosition.GetX() / DetailTextureScale));
|
|
int32_t newDetailTexturePosY = aznumeric_cast<int32_t>(AZStd::roundf(cameraPosition.GetY() / DetailTextureScale));
|
|
|
|
Aabb2i newBounds;
|
|
newBounds.m_min.m_x = newDetailTexturePosX - DetailTextureSizeHalf;
|
|
newBounds.m_min.m_y = newDetailTexturePosY - DetailTextureSizeHalf;
|
|
newBounds.m_max.m_x = newDetailTexturePosX + DetailTextureSizeHalf;
|
|
newBounds.m_max.m_y = newDetailTexturePosY + DetailTextureSizeHalf;
|
|
|
|
// Use modulo to find the center point in texture space. Care must be taken so negative values are
|
|
// handled appropriately (ie, we want -1 % 1024 to equal 1023, not -1)
|
|
Vector2i newCenter;
|
|
newCenter.m_x = (DetailTextureSize + (newDetailTexturePosX % DetailTextureSize)) % DetailTextureSize;
|
|
newCenter.m_y = (DetailTextureSize + (newDetailTexturePosY % DetailTextureSize)) % DetailTextureSize;
|
|
|
|
CheckUpdateDetailTexture(newBounds, newCenter);
|
|
|
|
m_detailTextureBounds = newBounds;
|
|
m_dirtyDetailRegion = AZ::Aabb::CreateNull();
|
|
|
|
m_previousCameraPosition = cameraPosition;
|
|
|
|
AZ::Vector4 detailAabb = AZ::Vector4(
|
|
m_detailTextureBounds.m_min.m_x * DetailTextureScale,
|
|
m_detailTextureBounds.m_min.m_y * DetailTextureScale,
|
|
m_detailTextureBounds.m_max.m_x * DetailTextureScale,
|
|
m_detailTextureBounds.m_max.m_y * DetailTextureScale
|
|
);
|
|
AZ::Vector2 detailUvOffset = AZ::Vector2(float(newCenter.m_x) / DetailTextureSize, float(newCenter.m_y) / DetailTextureSize);
|
|
|
|
terrainSrg->SetConstant(m_detailAabbPropertyIndex, detailAabb);
|
|
terrainSrg->SetConstant(m_detailHalfPixelUvPropertyIndex, 0.5f / DetailTextureSize);
|
|
terrainSrg->SetConstant(m_detailCenterPropertyIndex, detailUvOffset);
|
|
terrainSrg->SetImage(m_detailMaterialIdPropertyIndex, m_detailTextureImage);
|
|
|
|
m_detailMaterialDataBuffer.UpdateSrg(terrainSrg.get());
|
|
}
|
|
|
|
m_detailImageNeedsUpdate = false;
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask)
|
|
{
|
|
if ((dataChangedMask & TerrainDataChangedMask::SurfaceData) != 0)
|
|
{
|
|
m_dirtyDetailRegion.AddAabb(dirtyRegion);
|
|
}
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingCreated(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material)
|
|
{
|
|
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
|
|
|
|
// Validate that the surface tag is new
|
|
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
|
|
{
|
|
if (surface.m_surfaceTag == surfaceTag)
|
|
{
|
|
AZ_Error(TerrainDetailMaterialManagerName, false, "Already have a surface material mapping for this surface tag.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint16_t detailMaterialId = CreateOrUpdateDetailMaterial(material);
|
|
materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, detailMaterialId });
|
|
m_detailMaterials.GetData(detailMaterialId).refCount++;
|
|
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag)
|
|
{
|
|
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
|
|
|
|
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
|
|
{
|
|
if (surface.m_surfaceTag == surfaceTag)
|
|
{
|
|
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
|
|
|
|
if (surface.m_surfaceTag != materialRegion.m_materialsForSurfaces.back().m_surfaceTag)
|
|
{
|
|
AZStd::swap(surface, materialRegion.m_materialsForSurfaces.back());
|
|
}
|
|
materialRegion.m_materialsForSurfaces.pop_back();
|
|
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
|
|
return;
|
|
}
|
|
}
|
|
AZ_Error(TerrainDetailMaterialManagerName, false, "Could not find surface tag to destroy for OnTerrainSurfaceMaterialMappingDestroyed().");
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material)
|
|
{
|
|
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
|
|
|
|
bool found = false;
|
|
uint16_t materialId = CreateOrUpdateDetailMaterial(material);
|
|
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
|
|
{
|
|
if (surface.m_surfaceTag == surfaceTag)
|
|
{
|
|
found = true;
|
|
if (surface.m_detailMaterialId != materialId)
|
|
{
|
|
++m_detailMaterials.GetData(materialId).refCount;
|
|
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
|
|
surface.m_detailMaterialId = materialId;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
++m_detailMaterials.GetData(materialId).refCount;
|
|
materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, materialId });
|
|
}
|
|
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion)
|
|
{
|
|
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
|
|
materialRegion.m_region = newRegion;
|
|
m_dirtyDetailRegion.AddAabb(oldRegion);
|
|
m_dirtyDetailRegion.AddAabb(newRegion);
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::CheckDetailMaterialForDeletion(uint16_t detailMaterialId)
|
|
{
|
|
auto& detailMaterialData = m_detailMaterials.GetData(detailMaterialId);
|
|
if (--detailMaterialData.refCount == 0)
|
|
{
|
|
uint16_t bufferIndex = detailMaterialData.m_detailMaterialBufferIndex;
|
|
DetailMaterialShaderData& shaderData = m_detailMaterialShaderData.GetElement(bufferIndex);
|
|
|
|
for (uint16_t imageIndex :
|
|
{
|
|
shaderData.m_colorImageIndex,
|
|
shaderData.m_normalImageIndex,
|
|
shaderData.m_roughnessImageIndex,
|
|
shaderData.m_metalnessImageIndex,
|
|
shaderData.m_specularF0ImageIndex,
|
|
shaderData.m_occlusionImageIndex,
|
|
shaderData.m_heightImageIndex
|
|
})
|
|
{
|
|
if (imageIndex != InvalidImageIndex)
|
|
{
|
|
m_bindlessImageHandler->RemoveBindlessImage(imageIndex);
|
|
}
|
|
}
|
|
|
|
m_detailMaterialShaderData.Release(bufferIndex);
|
|
m_detailMaterials.RemoveIndex(detailMaterialId);
|
|
|
|
m_detailMaterialBufferNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
uint16_t TerrainDetailMaterialManager::CreateOrUpdateDetailMaterial(MaterialInstance material)
|
|
{
|
|
static constexpr uint16_t InvalidDetailMaterial = 0xFFFF;
|
|
uint16_t detailMaterialId = InvalidDetailMaterial;
|
|
|
|
for (auto& detailMaterialData : m_detailMaterials.GetDataVector())
|
|
{
|
|
if (detailMaterialData.m_assetId == material->GetAssetId())
|
|
{
|
|
detailMaterialId = m_detailMaterials.GetIndexForData(&detailMaterialData);
|
|
UpdateDetailMaterialData(detailMaterialId, material);
|
|
break;
|
|
}
|
|
}
|
|
|
|
AZ_Assert(m_detailMaterialShaderData.GetSize() < 0xFF, "Only 255 detail materials supported.");
|
|
|
|
if (detailMaterialId == InvalidDetailMaterial && m_detailMaterialShaderData.GetSize() < 0xFF)
|
|
{
|
|
detailMaterialId = m_detailMaterials.GetFreeSlotIndex();
|
|
auto& detailMaterialData = m_detailMaterials.GetData(detailMaterialId);
|
|
detailMaterialData.m_detailMaterialBufferIndex = aznumeric_cast<uint16_t>(m_detailMaterialShaderData.Reserve());
|
|
UpdateDetailMaterialData(detailMaterialId, material);
|
|
}
|
|
return detailMaterialId;
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::UpdateDetailMaterialData(uint16_t detailMaterialIndex, MaterialInstance material)
|
|
{
|
|
DetailMaterialData& materialData = m_detailMaterials.GetData(detailMaterialIndex);
|
|
DetailMaterialShaderData& shaderData = m_detailMaterialShaderData.GetElement(materialData.m_detailMaterialBufferIndex);
|
|
|
|
if (materialData.m_materialChangeId == material->GetCurrentChangeId())
|
|
{
|
|
return; // material hasn't changed, nothing to do
|
|
}
|
|
|
|
materialData.m_materialChangeId = material->GetCurrentChangeId();
|
|
materialData.m_assetId = material->GetAssetId();
|
|
|
|
DetailTextureFlags& flags = shaderData.m_flags;
|
|
|
|
auto getIndex = [&](const char* const indexName) -> AZ::RPI::MaterialPropertyIndex
|
|
{
|
|
const AZ::RPI::MaterialPropertyIndex index = material->FindPropertyIndex(AZ::Name(indexName));
|
|
AZ_Warning(TerrainDetailMaterialManagerName, index.IsValid(), "Failed to find shader input constant %s.", indexName);
|
|
return index;
|
|
};
|
|
|
|
auto applyProperty = [&](const char* const indexName, auto& ref) -> void
|
|
{
|
|
const auto index = getIndex(indexName);
|
|
if (index.IsValid())
|
|
{
|
|
// GetValue<T>() expects the actaul type, not a reference type, so the reference needs to be removed.
|
|
using TypeRefRemoved = AZStd::remove_cvref_t<decltype(ref)>;
|
|
ref = material->GetPropertyValue(index).GetValue<TypeRefRemoved>();
|
|
}
|
|
};
|
|
|
|
auto applyImage = [&](const char* const indexName, AZ::Data::Instance<AZ::RPI::Image>& ref, const char* const usingFlagName, DetailTextureFlags flagToSet, uint16_t& imageIndex) -> void
|
|
{
|
|
// Determine if an image exists and if its using flag allows it to be used.
|
|
const auto index = getIndex(indexName);
|
|
const auto useTextureIndex = getIndex(usingFlagName);
|
|
bool useTextureValue = true;
|
|
if (useTextureIndex.IsValid())
|
|
{
|
|
useTextureValue = material->GetPropertyValue(useTextureIndex).GetValue<bool>();
|
|
}
|
|
if (index.IsValid() && useTextureValue)
|
|
{
|
|
ref = material->GetPropertyValue(index).GetValue<AZ::Data::Instance<AZ::RPI::Image>>();
|
|
}
|
|
useTextureValue = useTextureValue && ref;
|
|
flags = DetailTextureFlags(useTextureValue ? (flags | flagToSet) : (flags & ~flagToSet));
|
|
|
|
// Update queues to add/remove textures depending on if the image is used
|
|
if (ref)
|
|
{
|
|
if (imageIndex == InvalidImageIndex)
|
|
{
|
|
imageIndex = m_bindlessImageHandler->AppendBindlessImage(ref->GetImageView());
|
|
}
|
|
else
|
|
{
|
|
m_bindlessImageHandler->UpdateBindlessImage(imageIndex, ref->GetImageView());
|
|
}
|
|
}
|
|
else if (imageIndex != InvalidImageIndex)
|
|
{
|
|
m_bindlessImageHandler->RemoveBindlessImage(imageIndex);
|
|
imageIndex = InvalidImageIndex;
|
|
}
|
|
};
|
|
|
|
auto applyFlag = [&](const char* const indexName, DetailTextureFlags flagToSet) -> void
|
|
{
|
|
const auto index = getIndex(indexName);
|
|
if (index.IsValid())
|
|
{
|
|
bool flagValue = material->GetPropertyValue(index).GetValue<bool>();
|
|
flags = DetailTextureFlags(flagValue ? flags | flagToSet : flags);
|
|
}
|
|
};
|
|
|
|
auto getEnumName = [&](const char* const indexName) -> const AZStd::string_view
|
|
{
|
|
const auto index = getIndex(indexName);
|
|
if (index.IsValid())
|
|
{
|
|
uint32_t enumIndex = material->GetPropertyValue(index).GetValue<uint32_t>();
|
|
const AZ::Name& enumName = material->GetMaterialPropertiesLayout()->GetPropertyDescriptor(index)->GetEnumName(enumIndex);
|
|
return enumName.GetStringView();
|
|
}
|
|
return "";
|
|
};
|
|
|
|
using namespace DetailMaterialInputs;
|
|
applyImage(BaseColorMap, materialData.m_colorImage, BaseColorUseTexture, DetailTextureFlags::UseTextureBaseColor, shaderData.m_colorImageIndex);
|
|
applyProperty(BaseColorFactor, shaderData.m_baseColorFactor);
|
|
|
|
const auto index = getIndex(BaseColorColor);
|
|
if (index.IsValid())
|
|
{
|
|
AZ::Color baseColor = material->GetPropertyValue(index).GetValue<AZ::Color>();
|
|
shaderData.m_baseColorRed = baseColor.GetR();
|
|
shaderData.m_baseColorGreen = baseColor.GetG();
|
|
shaderData.m_baseColorBlue = baseColor.GetB();
|
|
}
|
|
|
|
const AZStd::string_view& blendModeString = getEnumName(BaseColorBlendMode);
|
|
if (blendModeString == "Multiply")
|
|
{
|
|
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeMultiply);
|
|
}
|
|
else if (blendModeString == "LinearLight")
|
|
{
|
|
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeLinearLight);
|
|
}
|
|
else if (blendModeString == "Lerp")
|
|
{
|
|
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeLerp);
|
|
}
|
|
else if (blendModeString == "Overlay")
|
|
{
|
|
flags = DetailTextureFlags(flags | DetailTextureFlags::BlendModeOverlay);
|
|
}
|
|
|
|
applyImage(MetallicMap, materialData.m_metalnessImage, MetallicUseTexture, DetailTextureFlags::UseTextureMetallic, shaderData.m_metalnessImageIndex);
|
|
applyProperty(MetallicFactor, shaderData.m_metalFactor);
|
|
|
|
applyImage(RoughnessMap, materialData.m_roughnessImage, RoughnessUseTexture, DetailTextureFlags::UseTextureRoughness, shaderData.m_roughnessImageIndex);
|
|
|
|
if ((flags & DetailTextureFlags::UseTextureRoughness) > 0)
|
|
{
|
|
float lowerBound = 0.0;
|
|
float upperBound = 1.0;
|
|
applyProperty(RoughnessLowerBound, lowerBound);
|
|
applyProperty(RoughnessUpperBound, upperBound);
|
|
shaderData.m_roughnessBias = lowerBound;
|
|
shaderData.m_roughnessScale = upperBound - lowerBound;
|
|
}
|
|
else
|
|
{
|
|
shaderData.m_roughnessBias = 0.0;
|
|
applyProperty(RoughnessFactor, shaderData.m_roughnessScale);
|
|
}
|
|
|
|
applyImage(SpecularF0Map, materialData.m_specularF0Image, SpecularF0UseTexture, DetailTextureFlags::UseTextureSpecularF0, shaderData.m_specularF0ImageIndex);
|
|
applyProperty(SpecularF0Factor, shaderData.m_specularF0Factor);
|
|
|
|
applyImage(NormalMap, materialData.m_normalImage, NormalUseTexture, DetailTextureFlags::UseTextureNormal, shaderData.m_normalImageIndex);
|
|
applyProperty(NormalFactor, shaderData.m_normalFactor);
|
|
applyFlag(NormalFlipX, DetailTextureFlags::FlipNormalX);
|
|
applyFlag(NormalFlipY, DetailTextureFlags::FlipNormalY);
|
|
|
|
applyImage(DiffuseOcclusionMap, materialData.m_occlusionImage, DiffuseOcclusionUseTexture, DetailTextureFlags::UseTextureOcclusion, shaderData.m_occlusionImageIndex);
|
|
applyProperty(DiffuseOcclusionFactor, shaderData.m_occlusionFactor);
|
|
|
|
applyImage(HeightMap, materialData.m_heightImage, HeightUseTexture, DetailTextureFlags::UseTextureHeight, shaderData.m_heightImageIndex);
|
|
applyProperty(HeightFactor, shaderData.m_heightFactor);
|
|
applyProperty(HeightOffset, shaderData.m_heightOffset);
|
|
applyProperty(HeightBlendFactor, shaderData.m_heightBlendFactor);
|
|
|
|
m_detailMaterialBufferNeedsUpdate = true;
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::CheckUpdateDetailTexture(const Aabb2i& newBounds, const Vector2i& newCenter)
|
|
{
|
|
if (r_terrainDebugDetailImageUpdates)
|
|
{
|
|
AZ_Printf("TerrainDetailMaterialManager", "Old Bounds: m(%i, %i)M(%i, %i) New Bounds: m(%i, %i)M(%i, %i)",
|
|
m_detailTextureBounds.m_min.m_x, m_detailTextureBounds.m_min.m_y, m_detailTextureBounds.m_max.m_x, m_detailTextureBounds.m_max.m_y,
|
|
newBounds.m_min.m_x, newBounds.m_min.m_y, newBounds.m_max.m_x, newBounds.m_max.m_y
|
|
);
|
|
}
|
|
if (!m_detailTextureImage)
|
|
{
|
|
// If the m_detailTextureImage doesn't exist, create it and populate the entire texture
|
|
|
|
const AZ::Data::Instance<AZ::RPI::AttachmentImagePool> imagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
|
|
AZ::RHI::ImageDescriptor imageDescriptor = AZ::RHI::ImageDescriptor::Create2D(
|
|
AZ::RHI::ImageBindFlags::ShaderRead, DetailTextureSize, DetailTextureSize, AZ::RHI::Format::R8G8B8A8_UINT
|
|
);
|
|
const AZ::Name TerrainDetailName = AZ::Name(TerrainDetailChars);
|
|
m_detailTextureImage = AZ::RPI::AttachmentImage::Create(*imagePool.get(), imageDescriptor, TerrainDetailName, nullptr, nullptr);
|
|
AZ_Error(TerrainDetailMaterialManagerName, m_detailTextureImage, "Failed to initialize the detail texture image.");
|
|
|
|
UpdateDetailTexture(newBounds, newBounds, newCenter);
|
|
}
|
|
else
|
|
{
|
|
// If the new bounds of the detail texture are different than the old bounds, then the edges of the texture need to be updated.
|
|
|
|
int32_t offsetX = m_detailTextureBounds.m_min.m_x - newBounds.m_min.m_x;
|
|
|
|
// Horizontal edge update
|
|
if (newBounds.m_min.m_x != m_detailTextureBounds.m_min.m_x)
|
|
{
|
|
Aabb2i updateBounds;
|
|
if (newBounds.m_min.m_x < m_detailTextureBounds.m_min.m_x)
|
|
{
|
|
updateBounds.m_min.m_x = newBounds.m_min.m_x;
|
|
updateBounds.m_max.m_x = m_detailTextureBounds.m_min.m_x;
|
|
}
|
|
else
|
|
{
|
|
updateBounds.m_min.m_x = m_detailTextureBounds.m_max.m_x;
|
|
updateBounds.m_max.m_x = newBounds.m_max.m_x;
|
|
}
|
|
updateBounds.m_min.m_y = newBounds.m_min.m_y;
|
|
updateBounds.m_max.m_y = newBounds.m_max.m_y;
|
|
|
|
if (r_terrainDebugDetailImageUpdates)
|
|
{
|
|
AZ_Printf("TerrainDetailMaterialManager", "Updating horizontal edge: m(%i, %i)M(%i, %i)",
|
|
updateBounds.m_min.m_x, updateBounds.m_min.m_y, updateBounds.m_max.m_x, updateBounds.m_max.m_y);
|
|
}
|
|
UpdateDetailTexture(updateBounds, newBounds, newCenter);
|
|
}
|
|
|
|
// Vertical edge update
|
|
if (newBounds.m_min.m_y != m_detailTextureBounds.m_min.m_y)
|
|
{
|
|
Aabb2i updateBounds;
|
|
// Don't update areas that have already been updated in the horizontal update.
|
|
updateBounds.m_min.m_x = newBounds.m_min.m_x + AZ::GetMax(0, offsetX);
|
|
updateBounds.m_max.m_x = newBounds.m_max.m_x + AZ::GetMin(0, offsetX);
|
|
if (newBounds.m_min.m_y < m_detailTextureBounds.m_min.m_y)
|
|
{
|
|
updateBounds.m_min.m_y = newBounds.m_min.m_y;
|
|
updateBounds.m_max.m_y = m_detailTextureBounds.m_min.m_y;
|
|
}
|
|
else
|
|
{
|
|
updateBounds.m_min.m_y = m_detailTextureBounds.m_max.m_y;
|
|
updateBounds.m_max.m_y = newBounds.m_max.m_y;
|
|
}
|
|
|
|
if (r_terrainDebugDetailImageUpdates)
|
|
{
|
|
AZ_Printf("TerrainDetailMaterialManager", "Updating vertical edge: m(%i, %i)M(%i, %i)",
|
|
updateBounds.m_min.m_x, updateBounds.m_min.m_y, updateBounds.m_max.m_x, updateBounds.m_max.m_y);
|
|
}
|
|
UpdateDetailTexture(updateBounds, newBounds, newCenter);
|
|
}
|
|
|
|
if (m_dirtyDetailRegion.IsValid())
|
|
{
|
|
if (r_terrainDebugDetailImageUpdates)
|
|
{
|
|
AZ_Printf("TerrainDetailMaterialManager", "m_dirtyDetailRegion: m(%f, %f)M(%f, %f)",
|
|
m_dirtyDetailRegion.GetMin().GetX(), m_dirtyDetailRegion.GetMin().GetY(), m_dirtyDetailRegion.GetMax().GetX(), m_dirtyDetailRegion.GetMax().GetY());
|
|
}
|
|
// If any regions are marked as dirty, then they should be updated.
|
|
|
|
AZ::Vector3 currentMin = AZ::Vector3(newBounds.m_min.m_x * DetailTextureScale, newBounds.m_min.m_y * DetailTextureScale, -0.5f);
|
|
AZ::Vector3 currentMax = AZ::Vector3(newBounds.m_max.m_x * DetailTextureScale, newBounds.m_max.m_y * DetailTextureScale, 0.5f);
|
|
AZ::Aabb detailTextureCoverage = AZ::Aabb::CreateFromMinMax(currentMin, currentMax);
|
|
AZ::Vector3 previousMin = AZ::Vector3(m_detailTextureBounds.m_min.m_x * DetailTextureScale, m_detailTextureBounds.m_min.m_y * DetailTextureScale, -0.5f);
|
|
AZ::Vector3 previousMax = AZ::Vector3(m_detailTextureBounds.m_max.m_x * DetailTextureScale, m_detailTextureBounds.m_max.m_y * DetailTextureScale, 0.5f);
|
|
AZ::Aabb previousCoverage = AZ::Aabb::CreateFromMinMax(previousMin, previousMax);
|
|
|
|
// Area of texture not already updated by camera movement above.
|
|
AZ::Aabb clampedCoverage = previousCoverage.GetClamped(detailTextureCoverage);
|
|
|
|
// Clamp the dirty region to the area of the detail texture that is visible and not already updated.
|
|
clampedCoverage.Clamp(m_dirtyDetailRegion);
|
|
|
|
if (clampedCoverage.IsValid())
|
|
{
|
|
Aabb2i updateBounds;
|
|
updateBounds.m_min.m_x = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMin().GetX() / DetailTextureScale));
|
|
updateBounds.m_min.m_y = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMin().GetY() / DetailTextureScale));
|
|
updateBounds.m_max.m_x = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMax().GetX() / DetailTextureScale));
|
|
updateBounds.m_max.m_y = aznumeric_cast<int32_t>(AZStd::roundf(clampedCoverage.GetMax().GetY() / DetailTextureScale));
|
|
if (updateBounds.m_min.m_x < updateBounds.m_max.m_x && updateBounds.m_min.m_y < updateBounds.m_max.m_y)
|
|
{
|
|
|
|
if (r_terrainDebugDetailImageUpdates)
|
|
{
|
|
AZ_Printf("TerrainDetailMaterialManager", "Updating dirty region: m(%i, %i)M(%i, %i)",
|
|
updateBounds.m_min.m_x, updateBounds.m_min.m_y, updateBounds.m_max.m_x, updateBounds.m_max.m_y);
|
|
}
|
|
UpdateDetailTexture(updateBounds, newBounds, newCenter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::UpdateDetailTexture(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel)
|
|
{
|
|
if (!m_detailTextureImage)
|
|
{
|
|
return;
|
|
}
|
|
|
|
struct DetailMaterialPixel
|
|
{
|
|
uint8_t m_material1{ 255 };
|
|
uint8_t m_material2{ 255 };
|
|
uint8_t m_blend{ 0 }; // 0 = full weight on material1, 255 = full weight on material2
|
|
uint8_t m_padding{ 0 };
|
|
};
|
|
|
|
// Because the center of the detail texture may be offset, each update area may actually need to be split into
|
|
// up to 4 separate update areas in each sector of the quadrant.
|
|
AZStd::array<Aabb2i, 4> textureSpaceAreas;
|
|
AZStd::array<Aabb2i, 4> scaledWorldSpaceAreas;
|
|
uint8_t updateAreaCount = CalculateUpdateRegions(updateArea, textureBounds, centerPixel, textureSpaceAreas, scaledWorldSpaceAreas);
|
|
|
|
if (updateAreaCount > 0)
|
|
{
|
|
m_detailImageNeedsUpdate = true;
|
|
}
|
|
|
|
// Pull the data for each area updated and use it to construct an update for the detail material id texture.
|
|
for (uint8_t i = 0; i < updateAreaCount; ++i)
|
|
{
|
|
const Aabb2i& quadrantTextureArea = textureSpaceAreas[i];
|
|
const Aabb2i& quadrantWorldArea = scaledWorldSpaceAreas[i];
|
|
|
|
AZStd::vector<DetailMaterialPixel> pixels;
|
|
pixels.resize((quadrantWorldArea.m_max.m_x - quadrantWorldArea.m_min.m_x) * (quadrantWorldArea.m_max.m_y - quadrantWorldArea.m_min.m_y));
|
|
uint32_t index = 0;
|
|
|
|
for (int yPos = quadrantWorldArea.m_min.m_y; yPos < quadrantWorldArea.m_max.m_y; ++yPos)
|
|
{
|
|
for (int xPos = quadrantWorldArea.m_min.m_x; xPos < quadrantWorldArea.m_max.m_x; ++xPos)
|
|
{
|
|
AZ::Vector2 position = AZ::Vector2(xPos * DetailTextureScale, yPos * DetailTextureScale);
|
|
AzFramework::SurfaceData::SurfaceTagWeightList surfaceWeights;
|
|
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::GetSurfaceWeightsFromVector2, position, surfaceWeights, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, nullptr);
|
|
|
|
// Store the top two surface weights in the texture with m_blend storing the relative weight.
|
|
bool isFirstMaterial = true;
|
|
float firstWeight = 0.0f;
|
|
for (const auto& surfaceTagWeight : surfaceWeights)
|
|
{
|
|
if (surfaceTagWeight.m_weight > 0.0f)
|
|
{
|
|
AZ::Crc32 surfaceType = surfaceTagWeight.m_surfaceType;
|
|
uint16_t materialId = GetDetailMaterialForSurfaceTypeAndPosition(surfaceType, position);
|
|
if (materialId != m_detailMaterials.NoFreeSlot && materialId < 255)
|
|
{
|
|
if (isFirstMaterial)
|
|
{
|
|
pixels.at(index).m_material1 = aznumeric_cast<uint8_t>(materialId);
|
|
firstWeight = surfaceTagWeight.m_weight;
|
|
// m_blend only needs to be calculated is material 2 is found, otherwise the initial value of 0 is correct.
|
|
isFirstMaterial = false;
|
|
}
|
|
else
|
|
{
|
|
pixels.at(index).m_material2 = aznumeric_cast<uint8_t>(materialId);
|
|
float totalWeight = firstWeight + surfaceTagWeight.m_weight;
|
|
float blendWeight = 1.0f - (firstWeight / totalWeight);
|
|
pixels.at(index).m_blend = aznumeric_cast<uint8_t>(AZStd::round(blendWeight * 255.0f));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break; // since the list is ordered, no other materials are in the list with positive weights.
|
|
}
|
|
}
|
|
++index;
|
|
}
|
|
}
|
|
|
|
const int32_t left = quadrantTextureArea.m_min.m_x;
|
|
const int32_t top = quadrantTextureArea.m_min.m_y;
|
|
const int32_t width = quadrantTextureArea.m_max.m_x - quadrantTextureArea.m_min.m_x;
|
|
const int32_t height = quadrantTextureArea.m_max.m_y - quadrantTextureArea.m_min.m_y;
|
|
|
|
AZ::RHI::ImageUpdateRequest imageUpdateRequest;
|
|
imageUpdateRequest.m_imageSubresourcePixelOffset.m_left = aznumeric_cast<uint32_t>(left);
|
|
imageUpdateRequest.m_imageSubresourcePixelOffset.m_top = aznumeric_cast<uint32_t>(top);
|
|
imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerRow = width * sizeof(DetailMaterialPixel);
|
|
imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = width * height * sizeof(DetailMaterialPixel);
|
|
imageUpdateRequest.m_sourceSubresourceLayout.m_rowCount = height;
|
|
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_width = width;
|
|
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_height = height;
|
|
imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_depth = 1;
|
|
imageUpdateRequest.m_sourceData = pixels.data();
|
|
imageUpdateRequest.m_image = m_detailTextureImage->GetRHIImage();
|
|
|
|
m_detailTextureImage->UpdateImageContents(imageUpdateRequest);
|
|
}
|
|
}
|
|
|
|
uint8_t TerrainDetailMaterialManager::CalculateUpdateRegions(const Aabb2i& updateArea, const Aabb2i& textureBounds, const Vector2i& centerPixel,
|
|
AZStd::array<Aabb2i, 4>& textureSpaceAreas, AZStd::array<Aabb2i, 4>& scaledWorldSpaceAreas)
|
|
{
|
|
Vector2i centerOffset = { centerPixel.m_x - DetailTextureSizeHalf, centerPixel.m_y - DetailTextureSizeHalf };
|
|
|
|
int32_t quadrantXOffset = centerPixel.m_x < DetailTextureSizeHalf ? DetailTextureSize : -DetailTextureSize;
|
|
int32_t quadrantYOffset = centerPixel.m_y < DetailTextureSizeHalf ? DetailTextureSize : -DetailTextureSize;
|
|
|
|
uint8_t numQuadrants = 0;
|
|
|
|
// For each of the 4 quadrants:
|
|
auto calculateQuadrant = [&](Vector2i quadrantOffset)
|
|
{
|
|
Aabb2i offsetUpdateArea = updateArea + centerOffset + quadrantOffset;
|
|
Aabb2i updateSectionBounds = textureBounds.GetClamped(offsetUpdateArea);
|
|
if (updateSectionBounds.IsValid())
|
|
{
|
|
textureSpaceAreas[numQuadrants] = updateSectionBounds - textureBounds.m_min;
|
|
scaledWorldSpaceAreas[numQuadrants] = updateSectionBounds - centerOffset - quadrantOffset;
|
|
++numQuadrants;
|
|
}
|
|
};
|
|
|
|
calculateQuadrant({ 0, 0 });
|
|
calculateQuadrant({ quadrantXOffset, 0 });
|
|
calculateQuadrant({ 0, quadrantYOffset });
|
|
calculateQuadrant({ quadrantXOffset, quadrantYOffset });
|
|
|
|
return numQuadrants;
|
|
}
|
|
|
|
uint16_t TerrainDetailMaterialManager::GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position)
|
|
{
|
|
for (const auto& materialRegion : m_detailMaterialRegions.GetDataVector())
|
|
{
|
|
if (materialRegion.m_region.Contains(AZ::Vector3(position.GetX(), position.GetY(), 0.0f)))
|
|
{
|
|
for (const auto& materialSurface : materialRegion.m_materialsForSurfaces)
|
|
{
|
|
if (materialSurface.m_surfaceTag == surfaceType)
|
|
{
|
|
return m_detailMaterials.GetData(materialSurface.m_detailMaterialId).m_detailMaterialBufferIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return m_detailMaterials.NoFreeSlot;
|
|
}
|
|
|
|
auto TerrainDetailMaterialManager::FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container)
|
|
-> DetailMaterialListRegion*
|
|
{
|
|
for (DetailMaterialListRegion& data : container.GetDataVector())
|
|
{
|
|
if (data.m_entityId == entityId)
|
|
{
|
|
return &data;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
auto TerrainDetailMaterialManager::FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container)
|
|
-> DetailMaterialListRegion&
|
|
{
|
|
DetailMaterialListRegion* dataPtr = FindByEntityId(entityId, container);
|
|
if (dataPtr != nullptr)
|
|
{
|
|
return *dataPtr;
|
|
}
|
|
|
|
const uint16_t slotId = container.GetFreeSlotIndex();
|
|
AZ_Assert(slotId != AZ::Render::IndexedDataVector<TerrainDetailMaterialManager>::NoFreeSlot, "Ran out of indices");
|
|
|
|
DetailMaterialListRegion& data = container.GetData(slotId);
|
|
data.m_entityId = entityId;
|
|
return data;
|
|
}
|
|
|
|
void TerrainDetailMaterialManager::RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container)
|
|
{
|
|
for (DetailMaterialListRegion& data : container.GetDataVector())
|
|
{
|
|
if (data.m_entityId == entityId)
|
|
{
|
|
container.RemoveData(&data);
|
|
return;
|
|
}
|
|
}
|
|
AZ_Assert(false, "Entity Id not found in container.")
|
|
}
|
|
|
|
}
|