text: /*
   * 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
   *
   */
  
  #pragma once
  
  #include <AzCore/base.h>
  #include <AzCore/Math/Aabb.h>
  #include <AzCore/std/containers/array.h>
  
  #include <AzFramework/Terrain/TerrainDataRequestBus.h>
  
  #include <TerrainRenderer/Aabb2i.h>
  #include <TerrainRenderer/BindlessImageArrayHandler.h>
  #include <TerrainRenderer/ClipmapBounds.h>
  #include <TerrainRenderer/TerrainAreaMaterialRequestBus.h>
  #include <TerrainRenderer/Vector2i.h>
  
  #include <Atom/RPI.Public/Material/Material.h>
  #include <Atom/RPI.Public/Image/AttachmentImage.h>
  #include <Atom/RPI.Reflect/Image/Image.h>
  #include <Atom/Feature/Utils/IndexedDataVector.h>
  #include <Atom/Feature/Utils/SparseVector.h>
  #include <Atom/Feature/Utils/GpuBufferHandler.h>
  
  namespace Terrain
  {
      class TerrainDetailMaterialManager
          : private AzFramework::Terrain::TerrainDataNotificationBus::Handler
          , private TerrainAreaMaterialNotificationBus::Handler
      {
      public:
  
          AZ_RTTI(TerrainDetailMaterialManager, "{3CBAF88F-E3B1-43B8-97A5-999133188BCC}");
          AZ_DISABLE_COPY_MOVE(TerrainDetailMaterialManager);
  
          TerrainDetailMaterialManager() = default;
          ~TerrainDetailMaterialManager() = default;
          
          void Initialize(
              const AZStd::shared_ptr<AZ::Render::BindlessImageArrayHandler>& bindlessImageHandler,
              AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg);
          bool IsInitialized() const;
          void Reset();
          bool UpdateSrgIndices(AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& srg);
  
          void Update(const AZ::Vector3& cameraPosition, AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg);
  
      private:
          
          using MaterialInstance = AZ::Data::Instance<AZ::RPI::Material>;
          static constexpr auto InvalidImageIndex = AZ::Render::BindlessImageArrayHandler::InvalidImageIndex;
  
          enum DetailTextureFlags : uint32_t
          {
              UseTextureBaseColor =  0b0000'0000'0000'0000'0000'0000'0000'0001,
              UseTextureNormal =     0b0000'0000'0000'0000'0000'0000'0000'0010,
              UseTextureMetallic =   0b0000'0000'0000'0000'0000'0000'0000'0100,
              UseTextureRoughness =  0b0000'0000'0000'0000'0000'0000'0000'1000,
              UseTextureOcclusion =  0b0000'0000'0000'0000'0000'0000'0001'0000,
              UseTextureHeight =     0b0000'0000'0000'0000'0000'0000'0010'0000,
              UseTextureSpecularF0 = 0b0000'0000'0000'0000'0000'0000'0100'0000,
  
              FlipNormalX =          0b0000'0000'0000'0001'0000'0000'0000'0000,
              FlipNormalY =          0b0000'0000'0000'0010'0000'0000'0000'0000,
  
              BlendModeMask =        0b0000'0000'0000'1100'0000'0000'0000'0000,
              BlendModeLerp =        0b0000'0000'0000'0000'0000'0000'0000'0000,
              BlendModeLinearLight = 0b0000'0000'0000'0100'0000'0000'0000'0000,
              BlendModeMultiply =    0b0000'0000'0000'1000'0000'0000'0000'0000,
              BlendModeOverlay =     0b0000'0000'0000'1100'0000'0000'0000'0000,
          };
          
          struct DetailMaterialShaderData
          {
              // Uv
              AZStd::array<float, 12> m_uvTransform
              {
                  1.0, 0.0, 0.0, 0.0,
                  0.0, 1.0, 0.0, 0.0,
                  0.0, 0.0, 1.0, 0.0,
              };
  
              float m_baseColorRed{ 1.0f };
              float m_baseColorGreen{ 1.0f };
              float m_baseColorBlue{ 1.0f };
  
              // Factor / Scale / Bias for input textures
              float m_baseColorFactor{ 1.0f };
  
              float m_normalFactor{ 1.0f };
              float m_metalFactor{ 1.0f };
              float m_roughnessScale{ 1.0f };
              float m_roughnessBias{ 0.0f };
  
              float m_specularF0Factor{ 1.0f };
              float m_occlusionFactor{ 1.0f };
              float m_heightFactor{ 1.0f };
              float m_heightOffset{ 0.0f };
  
              float m_heightBlendFactor{ 0.5f };
  
              // Flags
              DetailTextureFlags m_flags{ 0 };
  
              // Image indices
              uint16_t m_colorImageIndex{ InvalidImageIndex };
              uint16_t m_normalImageIndex{ InvalidImageIndex };
              uint16_t m_roughnessImageIndex{ InvalidImageIndex };
              uint16_t m_metalnessImageIndex{ InvalidImageIndex };
  
              uint16_t m_specularF0ImageIndex{ InvalidImageIndex };
              uint16_t m_occlusionImageIndex{ InvalidImageIndex };
              uint16_t m_heightImageIndex{ InvalidImageIndex };
  
              // 16 byte aligned
              uint16_t m_padding1;
              uint32_t m_padding2;
              uint32_t m_padding3;
          };
          static_assert(sizeof(DetailMaterialShaderData) % 16 == 0, "DetailMaterialShaderData must be 16 byte aligned.");
  
          struct DetailMaterialData
          {
              AZ::Data::AssetId m_assetId;
              AZ::RPI::Material::ChangeId m_materialChangeId{AZ::RPI::Material::DEFAULT_CHANGE_ID};
              uint32_t refCount = 0;
              uint16_t m_detailMaterialBufferIndex{ 0xFFFF };
  
              AZ::Data::Instance<AZ::RPI::Image> m_colorImage;
              AZ::Data::Instance<AZ::RPI::Image> m_normalImage;
              AZ::Data::Instance<AZ::RPI::Image> m_roughnessImage;
              AZ::Data::Instance<AZ::RPI::Image> m_metalnessImage;
              AZ::Data::Instance<AZ::RPI::Image> m_specularF0Image;
              AZ::Data::Instance<AZ::RPI::Image> m_occlusionImage;
              AZ::Data::Instance<AZ::RPI::Image> m_heightImage;
          };
  
          struct DetailMaterialSurface
          {
              AZ::Crc32 m_surfaceTag;
              uint16_t m_detailMaterialId;
          };
  
          struct DetailMaterialListRegion
          {
              AZ::EntityId m_entityId;
              AZ::Aabb m_region{AZ::Aabb::CreateNull()};
              AZStd::vector<DetailMaterialSurface> m_materialsForSurfaces;
          };
          
          // System-level parameters
          static constexpr int32_t DetailTextureSize{ 1024 };
          static constexpr int32_t DetailTextureSizeHalf{ DetailTextureSize / 2 };
          static constexpr float DetailTextureScale{ 0.5f };
          
          // AzFramework::Terrain::TerrainDataNotificationBus overrides...
          void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override;
          
          // TerrainAreaMaterialNotificationBus overrides...
          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 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
          void RemoveAllImages();
  
          //! Creates or updates an existing detail material with settings from a material instance
          uint16_t CreateOrUpdateDetailMaterial(MaterialInstance material);
  
          //! Decrements the ref count on a detail material and removes it if it reaches 0
          void CheckDetailMaterialForDeletion(uint16_t detailMaterialId);
  
          //! Updates a specific detail material with settings from a material instance
          void UpdateDetailMaterialData(uint16_t detailMaterialIndex, MaterialInstance material);
  
          //! Checks to see if the detail material id texture needs to update based on the camera position. Any
          //! required updates are then executed.
          void CheckUpdateDetailTexture(const AZ::Vector3& cameraPosition);
  
          //! 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);
  
          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;
          AZ::Render::IndexedDataVector<DetailMaterialListRegion> m_detailMaterialRegions;
          AZ::Render::SparseVector<DetailMaterialShaderData> m_detailMaterialShaderData;
          AZ::Render::GpuBufferHandler m_detailMaterialDataBuffer;
          
          AZ::Aabb m_dirtyDetailRegion{ AZ::Aabb::CreateNull() };
          ClipmapBounds m_detailMaterialIdBounds;
  
          AZ::RHI::ShaderInputImageIndex m_detailMaterialIdPropertyIndex;
          AZ::RHI::ShaderInputBufferIndex m_detailMaterialDataIndex;
          AZ::RHI::ShaderInputConstantIndex m_detailScalePropertyIndex;
  
          bool m_isInitialized{ false };
          bool m_detailMaterialBufferNeedsUpdate{ false };
          bool m_detailImageNeedsUpdate{ false };
  
      };
  }
  