diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp index 5f2a50f903..fc4b821578 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp @@ -8,21 +8,19 @@ #include -#include -#include #include +#include #include -#include -#include -#include +#include #include +#include +#include #include -#include -#include - #include +#include +#include #include namespace Terrain @@ -38,15 +36,14 @@ namespace Terrain if (auto edit = serialize->GetEditContext()) { - edit->Class("Terrain Surface Gradient Mapping", "Mapping between a surface and a material.") + edit->Class("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() - ->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( "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::Edit::PropertyVisibility::Hide) + } + ); + } + + const AZ::Edit::ElementData* TerrainSurfaceMaterialsListConfig::GetDynamicData(const void* handlerPtr, const void* elementPtr, const AZ::Uuid& ) + { + const TerrainSurfaceMaterialsListConfig* owner = reinterpret_cast(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 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& 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(); diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h index 0e32cb12c0..96a7da60de 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h @@ -8,15 +8,18 @@ #pragma once -#include -#include #include #include +#include + +#include +#include + #include + #include #include - namespace LmbrCentral { template @@ -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 m_materialAsset; AZ::Data::Instance 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 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 diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h index ae36a2639e..a2225702ef 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainAreaMaterialRequestBus.h @@ -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 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 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 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, diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp index a00d2efe59..f817c138a7 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.cpp @@ -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(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(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(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(firstWeight, 0.0f, 1.0f); + pixels.at(index).m_blend = aznumeric_cast(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(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& container) @@ -784,5 +913,5 @@ namespace Terrain } AZ_Assert(false, "Entity Id not found in container.") } - + } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h index 3f63812e47..16423251ec 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainDetailMaterialManager.h @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -151,7 +152,11 @@ namespace Terrain AZ::EntityId m_entityId; AZ::Aabb m_region{AZ::Aabb::CreateNull()}; AZStd::vector m_materialsForSurfaces; + uint16_t m_defaultDetailMaterialId; }; + + using DetailMaterialContainer = AZ::Render::IndexedDataVector; + 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 material) override; + void OnTerrainDefaultSurfaceMaterialDestroyed(AZ::EntityId entityId) override; + void OnTerrainDefaultSurfaceMaterialChanged(AZ::EntityId entityId, AZ::Data::Instance 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; + bool ForSurfaceTag(DetailMaterialListRegion& materialRegion, + SurfaceData::SurfaceTag surfaceTag, DefaultMaterialSurfaceCallback callback); DetailMaterialListRegion* FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container); DetailMaterialListRegion& FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container); void RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector& container); - + AZStd::shared_ptr m_bindlessImageHandler; AZ::Data::Instance m_detailTextureImage; - AZ::Render::IndexedDataVector m_detailMaterials; + DetailMaterialContainer m_detailMaterials; AZ::Render::IndexedDataVector m_detailMaterialRegions; AZ::Render::SparseVector 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 }; - + }; }