Merge branch 'development' of https://github.com/o3de/o3de into TerrainMaterialsFix

This commit is contained in:
Sergey Pereslavtsev
2022-02-10 10:40:38 +00:00
325 changed files with 7728 additions and 5528 deletions
@@ -8,21 +8,19 @@
#include <TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
#include <TerrainSystem/TerrainSystemBus.h>
namespace Terrain
@@ -38,15 +36,14 @@ namespace Terrain
if (auto edit = serialize->GetEditContext())
{
edit->Class<TerrainSurfaceMaterialMapping>("Terrain Surface Gradient Mapping", "Mapping between a surface and a material.")
edit->Class<TerrainSurfaceMaterialMapping>("Terrain surface gradient mapping", "Mapping between a surface and a material.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &TerrainSurfaceMaterialMapping::m_surfaceTag, "Surface Tag", "Surface type to map to a material.")
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialMapping::m_materialAsset, "Material Asset", "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, true)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &TerrainSurfaceMaterialMapping::m_surfaceTag, "Surface tag", "Surface type to map to a material.")
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialMapping::m_materialAsset, "Material asset", "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, true)
;
}
}
@@ -60,7 +57,8 @@ namespace Terrain
if (serialize)
{
serialize->Class<TerrainSurfaceMaterialsListConfig, AZ::ComponentConfig>()
->Version(1)
->Version(2)
->Field("DefaultMaterial", &TerrainSurfaceMaterialsListConfig::m_defaultSurfaceMaterial)
->Field("Mappings", &TerrainSurfaceMaterialsListConfig::m_surfaceMaterials);
AZ::EditContext* edit = serialize->GetEditContext();
@@ -68,10 +66,13 @@ namespace Terrain
{
edit->Class<TerrainSurfaceMaterialsListConfig>(
"Terrain Surface Material List Component", "Provide mapping between surfaces and render materials.")
->SetDynamicEditDataProvider(&TerrainSurfaceMaterialsListConfig::GetDynamicData)
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialsListConfig::m_defaultSurfaceMaterial,
"Default Material", "The default material to fall back to where no other material surface mappings exist.")
->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialsListConfig::m_surfaceMaterials,
"Material Mappings", "Maps surfaces to materials.");
@@ -79,6 +80,26 @@ namespace Terrain
}
}
TerrainSurfaceMaterialsListConfig::TerrainSurfaceMaterialsListConfig()
{
m_hideSurfaceTagData.m_attributes.push_back(
{
AZ::Edit::Attributes::Visibility,
aznew AZ::Edit::AttributeData<AZ::Crc32>(AZ::Edit::PropertyVisibility::Hide)
}
);
}
const AZ::Edit::ElementData* TerrainSurfaceMaterialsListConfig::GetDynamicData(const void* handlerPtr, const void* elementPtr, const AZ::Uuid& )
{
const TerrainSurfaceMaterialsListConfig* owner = reinterpret_cast<const TerrainSurfaceMaterialsListConfig*>(handlerPtr);
if (elementPtr == &owner->m_defaultSurfaceMaterial.m_surfaceTag)
{
return &owner->m_hideSurfaceTagData;
}
return nullptr;
}
void TerrainSurfaceMaterialsListComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC_CE("TerrainMaterialProviderService"));
@@ -116,15 +137,21 @@ namespace Terrain
{
m_cachedAabb = AZ::Aabb::CreateNull();
auto checkLoadMaterial = [&](TerrainSurfaceMaterialMapping& material)
{
if (material.m_materialAsset.GetId().IsValid())
{
material.m_active = false;
material.m_materialAsset.QueueLoad();
AZ::Data::AssetBus::MultiHandler::BusConnect(material.m_materialAsset.GetId());
}
};
// Set all the materials as inactive and start loading.
checkLoadMaterial(m_configuration.m_defaultSurfaceMaterial);
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
if (surfaceMaterialMapping.m_materialAsset.GetId().IsValid())
{
surfaceMaterialMapping.m_active = false;
surfaceMaterialMapping.m_materialAsset.QueueLoad();
AZ::Data::AssetBus::MultiHandler::BusConnect(surfaceMaterialMapping.m_materialAsset.GetId());
}
checkLoadMaterial(surfaceMaterialMapping);
}
// Announce initial shape using OnShapeChanged
@@ -135,24 +162,35 @@ namespace Terrain
{
TerrainAreaMaterialRequestBus::Handler::BusDisconnect();
auto checkResetMaterial = [&](TerrainSurfaceMaterialMapping& material)
{
if (material.m_materialAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(material.m_materialAsset.GetId());
material.m_materialAsset.Release();
material.m_materialInstance.reset();
material.m_activeMaterialAssetId = AZ::Data::AssetId();
}
};
checkResetMaterial(m_configuration.m_defaultSurfaceMaterial);
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
if (surfaceMaterialMapping.m_materialAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(surfaceMaterialMapping.m_materialAsset.GetId());
surfaceMaterialMapping.m_materialAsset.Release();
surfaceMaterialMapping.m_materialInstance.reset();
surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId();
}
checkResetMaterial(surfaceMaterialMapping);
}
HandleMaterialStateChanges();
}
int TerrainSurfaceMaterialsListComponent::CountMaterialIDInstances(AZ::Data::AssetId id) const
int TerrainSurfaceMaterialsListComponent::CountMaterialIdInstances(AZ::Data::AssetId id) const
{
int count = 0;
if (m_configuration.m_defaultSurfaceMaterial.m_activeMaterialAssetId == id)
{
count++;
}
for (const auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
if (surfaceMaterialMapping.m_activeMaterialAssetId == id)
@@ -169,28 +207,63 @@ namespace Terrain
bool anyMaterialIsActive = false;
bool anyMaterialWasAlreadyActive = false;
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
const bool wasPreviouslyActive = surfaceMaterialMapping.m_active;
const bool isNowActive = (surfaceMaterialMapping.m_materialInstance != nullptr);
// Handle default material first
auto& defaultMaterial = m_configuration.m_defaultSurfaceMaterial;
if (wasPreviouslyActive)
{
anyMaterialWasAlreadyActive = true;
}
const bool wasPreviouslyActive = defaultMaterial.m_active;
defaultMaterial.m_active = (defaultMaterial.m_materialInstance != nullptr);
if (isNowActive)
{
anyMaterialIsActive = true;
}
surfaceMaterialMapping.m_active = isNowActive;
if (!wasPreviouslyActive && !isNowActive)
anyMaterialWasAlreadyActive = wasPreviouslyActive;
anyMaterialIsActive = defaultMaterial.m_active;
if (!wasPreviouslyActive && !defaultMaterial.m_active)
{
// A material has been assigned but has not yet completed loading.
}
else if (!wasPreviouslyActive && isNowActive)
else if (!wasPreviouslyActive && defaultMaterial.m_active)
{
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainDefaultSurfaceMaterialCreated, GetEntityId(),
defaultMaterial.m_materialInstance);
defaultMaterial.m_previousChangeId = defaultMaterial.m_materialInstance->GetCurrentChangeId();
}
else if (wasPreviouslyActive && !defaultMaterial.m_active)
{
// Don't disconnect from the AssetBus if this material is mapped more than once.
if (CountMaterialIdInstances(defaultMaterial.m_activeMaterialAssetId) == 1)
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(defaultMaterial.m_activeMaterialAssetId);
}
defaultMaterial = {};
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainDefaultSurfaceMaterialDestroyed, GetEntityId());
}
else if (defaultMaterial.m_materialInstance->GetAssetId() != defaultMaterial.m_activeMaterialAssetId ||
defaultMaterial.m_materialInstance->GetCurrentChangeId() != defaultMaterial.m_previousChangeId)
{
defaultMaterial.m_previousChangeId = defaultMaterial.m_materialInstance->GetCurrentChangeId();
defaultMaterial.m_activeMaterialAssetId = defaultMaterial.m_materialInstance->GetAssetId();
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainDefaultSurfaceMaterialChanged, GetEntityId(), defaultMaterial.m_materialInstance);
}
}
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
const bool wasPreviouslyActive = surfaceMaterialMapping.m_active;
surfaceMaterialMapping.m_active = surfaceMaterialMapping.m_materialInstance != nullptr;
anyMaterialWasAlreadyActive = anyMaterialWasAlreadyActive || wasPreviouslyActive;
anyMaterialIsActive = anyMaterialIsActive || surfaceMaterialMapping.m_active;
if (!wasPreviouslyActive && !surfaceMaterialMapping.m_active)
{
// A material has been assigned but has not yet completed loading.
}
else if (!wasPreviouslyActive && surfaceMaterialMapping.m_active)
{
// Remember the asset id so we can disconnect from the AssetBus if the material asset is removed.
surfaceMaterialMapping.m_activeMaterialAssetId = surfaceMaterialMapping.m_materialAsset.GetId();
@@ -199,27 +272,47 @@ namespace Terrain
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingCreated, GetEntityId(),
surfaceMaterialMapping.m_surfaceTag,
surfaceMaterialMapping.m_materialInstance);
surfaceMaterialMapping.m_previousChangeId = surfaceMaterialMapping.m_materialInstance->GetCurrentChangeId();
surfaceMaterialMapping.m_previousTag = surfaceMaterialMapping.m_surfaceTag;
}
else if (wasPreviouslyActive && !isNowActive)
else if (wasPreviouslyActive && !surfaceMaterialMapping.m_active)
{
// Don't disconnect from the AssetBus if this material is mapped more than once.
if (CountMaterialIDInstances(surfaceMaterialMapping.m_activeMaterialAssetId) == 1)
if (CountMaterialIdInstances(surfaceMaterialMapping.m_activeMaterialAssetId) == 1)
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(surfaceMaterialMapping.m_activeMaterialAssetId);
}
surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId();
surfaceMaterialMapping.m_activeMaterialAssetId = {};
surfaceMaterialMapping.m_previousChangeId = AZ::RPI::Material::DEFAULT_CHANGE_ID;
surfaceMaterialMapping.m_previousTag = {};
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingDestroyed, GetEntityId(),
surfaceMaterialMapping.m_surfaceTag);
}
else
else
{
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingChanged, GetEntityId(),
surfaceMaterialMapping.m_surfaceTag,
surfaceMaterialMapping.m_materialInstance);
if (surfaceMaterialMapping.m_previousTag != surfaceMaterialMapping.m_surfaceTag)
{
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingTagChanged, GetEntityId(),
surfaceMaterialMapping.m_previousTag,
surfaceMaterialMapping.m_surfaceTag);
surfaceMaterialMapping.m_previousTag = surfaceMaterialMapping.m_surfaceTag;
}
if (surfaceMaterialMapping.m_materialInstance->GetAssetId() != surfaceMaterialMapping.m_activeMaterialAssetId ||
surfaceMaterialMapping.m_materialInstance->GetCurrentChangeId() != surfaceMaterialMapping.m_previousChangeId)
{
surfaceMaterialMapping.m_previousChangeId = surfaceMaterialMapping.m_materialInstance->GetCurrentChangeId();
surfaceMaterialMapping.m_activeMaterialAssetId = surfaceMaterialMapping.m_materialInstance->GetAssetId();
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingMaterialChanged, GetEntityId(),
surfaceMaterialMapping.m_surfaceTag,
surfaceMaterialMapping.m_materialInstance);
}
}
}
@@ -290,14 +383,28 @@ namespace Terrain
void TerrainSurfaceMaterialsListComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
// Find the missing material instance with the correct id.
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
auto checkUpdateMaterialAsset = [](TerrainSurfaceMaterialMapping& mapping, const AZ::Data::Asset<AZ::Data::AssetData>& asset) -> bool
{
if (surfaceMaterialMapping.m_materialAsset.GetId() == asset.GetId() &&
(!surfaceMaterialMapping.m_materialInstance ||
surfaceMaterialMapping.m_materialInstance->GetAssetId() != surfaceMaterialMapping.m_materialAsset.GetId()))
if (mapping.m_materialAsset.GetId() == asset.GetId() &&
(!mapping.m_materialInstance || mapping.m_materialInstance->GetAssetId() != mapping.m_materialAsset.GetId()))
{
surfaceMaterialMapping.m_materialInstance = AZ::RPI::Material::FindOrCreate(surfaceMaterialMapping.m_materialAsset);
surfaceMaterialMapping.m_materialAsset.Release();
mapping.m_materialInstance = AZ::RPI::Material::FindOrCreate(mapping.m_materialAsset);
mapping.m_materialAsset.Release();
return true;
}
return false;
};
// First check the default material
if (!checkUpdateMaterialAsset(m_configuration.m_defaultSurfaceMaterial, asset))
{
// If the default materail wasn't updated, then check all the surface material mappings.
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
if (checkUpdateMaterialAsset(surfaceMaterialMapping, asset))
{
break;
}
}
}
HandleMaterialStateChanges();
@@ -8,15 +8,18 @@
#pragma once
#include <Atom/Feature/Material/MaterialAssignment.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/EditContext.h>
#include <Atom/Feature/Material/MaterialAssignment.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <SurfaceData/SurfaceDataTypes.h>
#include <TerrainRenderer/TerrainAreaMaterialRequestBus.h>
namespace LmbrCentral
{
template<typename, typename>
@@ -27,16 +30,20 @@ namespace Terrain
{
struct TerrainSurfaceMaterialMapping final
{
public:
AZ_CLASS_ALLOCATOR(TerrainSurfaceMaterialMapping, AZ::SystemAllocator, 0);
AZ_RTTI(TerrainSurfaceMaterialMapping, "{37D2A586-CDDD-4FB7-A7D6-0B4CC575AB8C}");
static void Reflect(AZ::ReflectContext* context);
SurfaceData::SurfaceTag m_surfaceTag;
AZ::Data::AssetId m_activeMaterialAssetId;
AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialAsset;
AZ::Data::Instance<AZ::RPI::Material> m_materialInstance;
AZ::Data::AssetId m_activeMaterialAssetId;
AZ::RPI::Material::ChangeId m_previousChangeId = AZ::RPI::Material::DEFAULT_CHANGE_ID;
// Surface tags not used by default material
SurfaceData::SurfaceTag m_surfaceTag;
SurfaceData::SurfaceTag m_previousTag;
bool m_active = false;
};
@@ -47,7 +54,13 @@ namespace Terrain
AZ_RTTI(TerrainSurfaceMaterialsListConfig, "{68A1CB1B-C835-4C3A-8D1C-08692E07711A}", AZ::ComponentConfig);
static void Reflect(AZ::ReflectContext* context);
TerrainSurfaceMaterialsListConfig();
TerrainSurfaceMaterialMapping m_defaultSurfaceMaterial;
AZStd::vector<TerrainSurfaceMaterialMapping> m_surfaceMaterials;
private:
static const AZ::Edit::ElementData* GetDynamicData(const void* handlerPtr, const void* elementPtr, const AZ::Uuid& elementType);
AZ::Edit::ElementData m_hideSurfaceTagData;
};
class TerrainSurfaceMaterialsListComponent
@@ -78,7 +91,7 @@ namespace Terrain
private:
void HandleMaterialStateChanges();
int CountMaterialIDInstances(AZ::Data::AssetId id) const;
int CountMaterialIdInstances(AZ::Data::AssetId id) const;
////////////////////////////////////////////////////////////////////////
// ShapeComponentNotificationsBus
@@ -45,6 +45,27 @@ namespace Terrain
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
//! The default surface material has been assigned and loaded
virtual void OnTerrainDefaultSurfaceMaterialCreated(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] AZ::Data::Instance<AZ::RPI::Material> material)
{
}
//! The default surface material has been unassigned
virtual void OnTerrainDefaultSurfaceMaterialDestroyed(
[[maybe_unused]] AZ::EntityId entityId)
{
}
//! The default surface material has been changed to a different material
virtual void OnTerrainDefaultSurfaceMaterialChanged(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] AZ::Data::Instance<AZ::RPI::Material> newMaterial)
{
}
//! A loaded material mapped to a valid surface tag has been created
virtual void OnTerrainSurfaceMaterialMappingCreated(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] SurfaceData::SurfaceTag surface,
@@ -52,19 +73,30 @@ namespace Terrain
{
}
//! Either the material or surface tag was unassigned, making this mapping invalid
virtual void OnTerrainSurfaceMaterialMappingDestroyed(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] SurfaceData::SurfaceTag surface)
{
}
virtual void OnTerrainSurfaceMaterialMappingChanged(
//! The surface tag has changed to tag for an existing material
virtual void OnTerrainSurfaceMaterialMappingTagChanged(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] SurfaceData::SurfaceTag oldSurface,
[[maybe_unused]] SurfaceData::SurfaceTag newSurface)
{
}
//! The material has changed for an existing surface tag
virtual void OnTerrainSurfaceMaterialMappingMaterialChanged(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] SurfaceData::SurfaceTag surface,
[[maybe_unused]] AZ::Data::Instance<AZ::RPI::Material> material)
{
}
//! The bounds of this set of surface material mappings has changed
virtual void OnTerrainSurfaceMaterialMappingRegionChanged(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] const AZ::Aabb& oldRegion,
@@ -106,6 +106,8 @@ namespace Terrain
return;
}
InitializePassthroughDetailMaterial();
ClipmapBoundsDescriptor desc;
desc.m_clipmapUpdateMultiple = 1;
desc.m_clipToWorldScale = DetailTextureScale;
@@ -260,20 +262,73 @@ namespace Terrain
m_dirtyDetailRegion.AddAabb(dirtyRegion);
}
}
bool TerrainDetailMaterialManager::ForSurfaceTag(DetailMaterialListRegion& materialRegion,
SurfaceData::SurfaceTag surfaceTag, DefaultMaterialSurfaceCallback callback)
{
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
{
if (surface.m_surfaceTag == surfaceTag)
{
callback(surface);
return true;
}
}
return false;
}
void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialCreated(AZ::EntityId entityId, MaterialInstance material)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
AZ_Error("TerrainDetailMaterialManager", materialRegion.m_defaultDetailMaterialId == InvalidDetailMaterailId,
"Default detail material created but was already set for this region.");
materialRegion.m_defaultDetailMaterialId = CreateOrUpdateDetailMaterial(material);
m_detailMaterials.GetData(materialRegion.m_defaultDetailMaterialId).refCount++;
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
}
void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialDestroyed(AZ::EntityId entityId)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainDefaultSurfaceMaterialDestroyed() called for region that doesn't exist.");
return;
}
CheckDetailMaterialForDeletion(materialRegion->m_defaultDetailMaterialId);
materialRegion->m_defaultDetailMaterialId = InvalidDetailMaterailId;
}
void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialChanged(AZ::EntityId entityId, MaterialInstance newMaterial)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainDefaultSurfaceMaterialChanged() called for region that doesn't exist.");
return;
}
// Update existing entry or create a new material entry
uint16_t materialId = CreateOrUpdateDetailMaterial(newMaterial);
if (materialRegion->m_defaultDetailMaterialId != materialId)
{
++m_detailMaterials.GetData(materialId).refCount;
CheckDetailMaterialForDeletion(materialRegion->m_defaultDetailMaterialId);
materialRegion->m_defaultDetailMaterialId = materialId;
}
}
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)
ForSurfaceTag(materialRegion, surfaceTag, [](DetailMaterialSurface&)
{
if (surface.m_surfaceTag == surfaceTag)
{
AZ_Error(TerrainDetailMaterialManagerName, false, "Already have a surface material mapping for this surface tag.");
return;
}
}
AZ_Error(TerrainDetailMaterialManagerName, false, "Already have a surface material mapping for this surface tag.");
});
uint16_t detailMaterialId = CreateOrUpdateDetailMaterial(material);
materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, detailMaterialId });
@@ -284,52 +339,71 @@ namespace Terrain
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
[[maybe_unused]] bool found = ForSurfaceTag(materialRegion, surfaceTag,
[&](DetailMaterialSurface& surface)
{
if (surface.m_surfaceTag == surfaceTag)
{
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
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;
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, found, "Could not find surface tag to destroy for OnTerrainSurfaceMaterialMappingDestroyed().");
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingMaterialChanged(
AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainSurfaceMaterialMappingMaterialChanged() called for region that doesn't exist.");
return;
}
AZ_Error(TerrainDetailMaterialManagerName, false, "Could not find surface tag to destroy for OnTerrainSurfaceMaterialMappingDestroyed().");
// Update existing entry or create a new material entry
uint16_t materialId = CreateOrUpdateDetailMaterial(material);
[[maybe_unused]] bool found = ForSurfaceTag(*materialRegion, surfaceTag,
[&](DetailMaterialSurface& surface)
{
if (surface.m_detailMaterialId != materialId)
{
// Updated material was a different asset than the old material, decrement ref count and
// delete if no other surface tags are using it.
++m_detailMaterials.GetData(materialId).refCount;
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
surface.m_detailMaterialId = materialId;
}
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
});
AZ_Assert(found, "OnTerrainSurfaceMaterialMappingMaterialChanged() called for tag that doesn't exist.");
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material)
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingTagChanged(
AZ::EntityId entityId, SurfaceData::SurfaceTag oldTag, SurfaceData::SurfaceTag newTag)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
bool found = false;
uint16_t materialId = CreateOrUpdateDetailMaterial(material);
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
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;
}
AZ_Assert(false, "OnTerrainSurfaceMaterialMappingTagChanged() called for region that doesn't exist.");
return;
}
if (!found)
[[maybe_unused]] bool found = ForSurfaceTag(*materialRegion, oldTag,
[&](DetailMaterialSurface& surface)
{
++m_detailMaterials.GetData(materialId).refCount;
materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, materialId });
}
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
surface.m_surfaceTag = newTag;
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
});
AZ_Assert(found, "OnTerrainSurfaceMaterialMappingTagChanged() called for tag that doesn't exist.");
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion)
@@ -667,23 +741,38 @@ namespace Terrain
bool isFirstMaterial = true;
float firstWeight = 0.0f;
AZ::Vector2 position(surfacePoint.m_position.GetX(), surfacePoint.m_position.GetY());
const DetailMaterialListRegion* region = FindRegionForPosition(position);
if (region == nullptr)
{
pixels.at(index).m_material1 = m_passthroughMaterialId;
++index;
return;
}
for (const auto& surfaceTagWeight : surfacePoint.m_surfaceTags)
{
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)
uint16_t materialId = GetDetailMaterialForSurfaceType(*region, surfaceType);
if (materialId < 255)
{
if (isFirstMaterial)
{
// First material is valid. Save its weight to calculate blend later
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;
static constexpr float MaxValueBeforeRounding = 254.5f / 255.0f;
if (firstWeight >= MaxValueBeforeRounding)
{
break;
}
}
else
{
// Second material is valid, weight is relative based on first material's weight.
pixels.at(index).m_material2 = aznumeric_cast<uint8_t>(materialId);
float totalWeight = firstWeight + surfaceTagWeight.m_weight;
float blendWeight = 1.0f - (firstWeight / totalWeight);
@@ -691,11 +780,37 @@ namespace Terrain
break;
}
}
continue; // search for second material
}
else
{
break; // since the list is ordered, no other materials are in the list with positive weights.
// No more valid materials in list since surfaceTagWeight is ordered.
uint8_t defaultMaterial = region->m_defaultDetailMaterialId == InvalidDetailMaterailId ? m_passthroughMaterialId :
aznumeric_cast<uint8_t>(m_detailMaterials.GetData(region->m_defaultDetailMaterialId).m_detailMaterialBufferIndex);
if (isFirstMaterial)
{
// Only one material and it's the default material.
pixels.at(index).m_material1 = defaultMaterial;
}
else
{
// Second material is default, weight is exactly what the first material requested
pixels.at(index).m_material2 = defaultMaterial;
float blendWeight = 1.0f - AZStd::clamp<float>(firstWeight, 0.0f, 1.0f);
pixels.at(index).m_blend = aznumeric_cast<uint8_t>(AZStd::round(blendWeight * 255.0f));
}
}
if (pixels.at(index).m_material1 == pixels.at(index).m_material2)
{
// If the materials are the same, then make the blend 100% on the first id so the shader
// doesn't blend identical materials
pixels.at(index).m_blend = 0;
}
break;
}
++index;
};
@@ -723,23 +838,37 @@ namespace Terrain
m_detailTextureImage->UpdateImageContents(imageUpdateRequest);
}
uint16_t TerrainDetailMaterialManager::GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position)
uint16_t TerrainDetailMaterialManager::GetDetailMaterialForSurfaceType(const DetailMaterialListRegion& materialRegion, AZ::Crc32 surfaceType) const
{
for (const auto& materialSurface : materialRegion.m_materialsForSurfaces)
{
if (materialSurface.m_surfaceTag == surfaceType)
{
return m_detailMaterials.GetData(materialSurface.m_detailMaterialId).m_detailMaterialBufferIndex;
}
}
return InvalidDetailMaterailId;
}
auto TerrainDetailMaterialManager::FindRegionForPosition(const AZ::Vector2& position) const -> const DetailMaterialListRegion*
{
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 &materialRegion;
}
}
return m_detailMaterials.NoFreeSlot;
return nullptr;
}
void TerrainDetailMaterialManager::InitializePassthroughDetailMaterial()
{
m_passthroughMaterialId = aznumeric_cast<uint8_t>(m_detailMaterialShaderData.Reserve());
DetailMaterialShaderData& materialShaderData = m_detailMaterialShaderData.GetElement(m_passthroughMaterialId);
// Material defaults to white (1.0, 1.0, 1.0), set the blend mode to multiply so it passes through to the macro material.
materialShaderData.m_flags = DetailTextureFlags::BlendModeMultiply;
}
auto TerrainDetailMaterialManager::FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container)
@@ -784,5 +913,5 @@ namespace Terrain
}
AZ_Assert(false, "Entity Id not found in container.")
}
}
@@ -11,6 +11,7 @@
#include <AzCore/base.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/function/function_template.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
@@ -151,7 +152,11 @@ namespace Terrain
AZ::EntityId m_entityId;
AZ::Aabb m_region{AZ::Aabb::CreateNull()};
AZStd::vector<DetailMaterialSurface> m_materialsForSurfaces;
uint16_t m_defaultDetailMaterialId;
};
using DetailMaterialContainer = AZ::Render::IndexedDataVector<DetailMaterialData>;
static constexpr auto InvalidDetailMaterailId = DetailMaterialContainer::NoFreeSlot;
// System-level parameters
static constexpr int32_t DetailTextureSize{ 1024 };
@@ -162,9 +167,14 @@ namespace Terrain
void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override;
// TerrainAreaMaterialNotificationBus overrides...
void OnTerrainDefaultSurfaceMaterialCreated(AZ::EntityId entityId, AZ::Data::Instance<AZ::RPI::Material> material) override;
void OnTerrainDefaultSurfaceMaterialDestroyed(AZ::EntityId entityId) override;
void OnTerrainDefaultSurfaceMaterialChanged(AZ::EntityId entityId, AZ::Data::Instance<AZ::RPI::Material> newMaterial) override;
void OnTerrainSurfaceMaterialMappingCreated(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override;
void OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag) override;
void OnTerrainSurfaceMaterialMappingChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override;
void OnTerrainSurfaceMaterialMappingMaterialChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override;
void OnTerrainSurfaceMaterialMappingTagChanged(
AZ::EntityId entityId, SurfaceData::SurfaceTag oldSurfaceTag, SurfaceData::SurfaceTag newSurfaceTag) override;
void OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) override;
//! Removes all images from all detail materials from the bindless image array
@@ -186,22 +196,32 @@ namespace Terrain
//! Updates the detail texture in a given area
void UpdateDetailTexture(const AZ::Aabb& worldUpdateAabb, const Aabb2i& textureUpdateAabb);
//! Finds the detail material Id for a surface type and position
uint16_t GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position);
//! Finds the detail material Id for a region and surface type
uint16_t GetDetailMaterialForSurfaceType(const DetailMaterialListRegion& materialRegion, AZ::Crc32 surfaceType) const;
//! Finds a region for a position. Returns nullptr if none found.
const DetailMaterialListRegion* FindRegionForPosition(const AZ::Vector2& position) const;
//! Initializes shader data for the default passthrough material which is used when no other detail material is found.
void InitializePassthroughDetailMaterial();
using DefaultMaterialSurfaceCallback = AZStd::function<void(DetailMaterialSurface&)>;
bool ForSurfaceTag(DetailMaterialListRegion& materialRegion,
SurfaceData::SurfaceTag surfaceTag, DefaultMaterialSurfaceCallback callback);
DetailMaterialListRegion* FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container);
DetailMaterialListRegion& FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container);
void RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container);
AZStd::shared_ptr<AZ::Render::BindlessImageArrayHandler> m_bindlessImageHandler;
AZ::Data::Instance<AZ::RPI::AttachmentImage> m_detailTextureImage;
AZ::Render::IndexedDataVector<DetailMaterialData> m_detailMaterials;
DetailMaterialContainer m_detailMaterials;
AZ::Render::IndexedDataVector<DetailMaterialListRegion> m_detailMaterialRegions;
AZ::Render::SparseVector<DetailMaterialShaderData> m_detailMaterialShaderData;
AZ::Render::GpuBufferHandler m_detailMaterialDataBuffer;
uint8_t m_passthroughMaterialId = 0;
AZ::Aabb m_dirtyDetailRegion{ AZ::Aabb::CreateNull() };
ClipmapBounds m_detailMaterialIdBounds;
@@ -212,6 +232,6 @@ namespace Terrain
bool m_isInitialized{ false };
bool m_detailMaterialBufferNeedsUpdate{ false };
bool m_detailImageNeedsUpdate{ false };
};
}