TerrainMacroMaterialComponent.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <TerrainRenderer/Components/TerrainMacroMaterialComponent.h>
  9. #include <TerrainSystem/TerrainSystemBus.h>
  10. #include <AzCore/Asset/AssetSerializer.h>
  11. #include <AzCore/Asset/AssetManager.h>
  12. #include <AzCore/Asset/AssetManagerBus.h>
  13. #include <AzCore/Component/Entity.h>
  14. #include <AzCore/RTTI/BehaviorContext.h>
  15. #include <AzCore/Serialization/EditContext.h>
  16. #include <AzCore/Serialization/SerializeContext.h>
  17. #include <Atom/RPI.Public/Image/AttachmentImage.h>
  18. #include <Atom/RPI.Public/Image/AttachmentImagePool.h>
  19. #include <Atom/RPI.Public/Image/ImageSystemInterface.h>
  20. #include <Atom/RPI.Public/Image/StreamingImage.h>
  21. #include <Atom/RPI.Public/RPIUtils.h>
  22. #include <LmbrCentral/Dependency/DependencyMonitor.h>
  23. namespace Terrain
  24. {
  25. bool TerrainMacroMaterialConfig::NormalMapAttributesAreReadOnly() const
  26. {
  27. return !m_macroNormalAsset.GetId().IsValid();
  28. }
  29. void TerrainMacroMaterialConfig::Reflect(AZ::ReflectContext* context)
  30. {
  31. if (auto* serialize = azrtti_cast<AZ::SerializeContext*>(context); serialize)
  32. {
  33. serialize->Class<TerrainMacroMaterialConfig, AZ::ComponentConfig>()
  34. ->Version(1)
  35. ->Field("MacroColor", &TerrainMacroMaterialConfig::m_macroColorAsset)
  36. ->Field("MacroNormal", &TerrainMacroMaterialConfig::m_macroNormalAsset)
  37. ->Field("NormalFlipX", &TerrainMacroMaterialConfig::m_normalFlipX)
  38. ->Field("NormalFlipY", &TerrainMacroMaterialConfig::m_normalFlipY)
  39. ->Field("NormalFactor", &TerrainMacroMaterialConfig::m_normalFactor)
  40. ->Field("Priority", &TerrainMacroMaterialConfig::m_priority)
  41. ;
  42. }
  43. }
  44. AZStd::string TerrainMacroMaterialConfig::GetMacroColorAssetPropertyName() const
  45. {
  46. return m_macroColorAssetPropertyLabel;
  47. }
  48. void TerrainMacroMaterialConfig::SetMacroColorAssetPropertyName(const AZStd::string& macroColorAssetPropertyName)
  49. {
  50. m_macroColorAssetPropertyLabel = macroColorAssetPropertyName;
  51. }
  52. void TerrainMacroMaterialComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  53. {
  54. services.push_back(AZ_CRC_CE("TerrainMacroMaterialProviderService"));
  55. }
  56. void TerrainMacroMaterialComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  57. {
  58. services.push_back(AZ_CRC_CE("TerrainMacroMaterialProviderService"));
  59. }
  60. void TerrainMacroMaterialComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  61. {
  62. services.push_back(AZ_CRC_CE("AxisAlignedBoxShapeService"));
  63. }
  64. void TerrainMacroMaterialComponent::Reflect(AZ::ReflectContext* context)
  65. {
  66. TerrainMacroMaterialConfig::Reflect(context);
  67. TerrainMacroMaterialRequests::Reflect(context);
  68. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  69. if (serialize)
  70. {
  71. serialize->Class<TerrainMacroMaterialComponent, AZ::Component>()
  72. ->Version(0)
  73. ->Field("Configuration", &TerrainMacroMaterialComponent::m_configuration)
  74. ;
  75. }
  76. }
  77. TerrainMacroMaterialComponent::TerrainMacroMaterialComponent(const TerrainMacroMaterialConfig& configuration)
  78. : m_configuration(configuration)
  79. {
  80. }
  81. void TerrainMacroMaterialComponent::Activate()
  82. {
  83. // Clear out our shape bounds and make sure the texture assets are queued to load.
  84. m_cachedShapeBounds = AZ::Aabb::CreateNull();
  85. m_configuration.m_macroColorAsset.QueueLoad();
  86. m_configuration.m_macroNormalAsset.QueueLoad();
  87. // Don't mark our material as active until it's finished loading and is valid.
  88. m_macroMaterialActive = false;
  89. // Listen for the texture assets to complete loading.
  90. AZ::Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_macroColorAsset.GetId());
  91. AZ::Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_macroNormalAsset.GetId());
  92. }
  93. void TerrainMacroMaterialComponent::Deactivate()
  94. {
  95. TerrainMacroMaterialRequestBus::Handler::BusDisconnect();
  96. AZ::Data::AssetBus::MultiHandler::BusDisconnect();
  97. m_configuration.m_macroColorAsset.Release();
  98. m_configuration.m_macroNormalAsset.Release();
  99. m_colorImage.reset();
  100. m_normalImage.reset();
  101. DestroyMacroColorImageModificationBuffer();
  102. // Send out any notifications as appropriate based on the macro material destruction.
  103. HandleMaterialStateChange();
  104. }
  105. bool TerrainMacroMaterialComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
  106. {
  107. if (auto config = azrtti_cast<const TerrainMacroMaterialConfig*>(baseConfig))
  108. {
  109. AZ_Assert(m_numImageModificationsActive == 0,
  110. "Changing the configuration during an image modification session might result in a broken modification state.")
  111. m_configuration = *config;
  112. return true;
  113. }
  114. return false;
  115. }
  116. bool TerrainMacroMaterialComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
  117. {
  118. if (auto config = azrtti_cast<TerrainMacroMaterialConfig*>(outBaseConfig))
  119. {
  120. *config = m_configuration;
  121. return true;
  122. }
  123. return false;
  124. }
  125. void TerrainMacroMaterialComponent::OnShapeChanged([[maybe_unused]] ShapeComponentNotifications::ShapeChangeReasons reasons)
  126. {
  127. // This should only get called while the macro material is active. If it gets called while the macro material isn't active,
  128. // we've got a bug where we haven't managed the bus connections properly.
  129. AZ_Assert(m_macroMaterialActive, "The ShapeComponentNotificationBus connection is out of sync with the material load.");
  130. AZ::Aabb oldShapeBounds = m_cachedShapeBounds;
  131. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  132. m_cachedShapeBounds, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
  133. TerrainMacroMaterialNotificationBus::Broadcast(
  134. &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialRegionChanged,
  135. GetEntityId(), oldShapeBounds, m_cachedShapeBounds);
  136. NotifyTerrainSystemOfColorChange(oldShapeBounds);
  137. NotifyTerrainSystemOfColorChange(m_cachedShapeBounds);
  138. }
  139. void TerrainMacroMaterialComponent::HandleMaterialStateChange()
  140. {
  141. // We only want our component to appear active during the time that the macro material is fully loaded and valid. The logic below
  142. // will handle all transition possibilities to notify if we've become active, inactive, or just changed. We'll also only
  143. // keep a valid up-to-date copy of the shape bounds while the material is valid, since we don't need it any other time.
  144. // Color and normal data is considered ready if it's finished loading or if we don't have a texture specified
  145. bool colorReady = m_colorImage || (!m_configuration.m_macroColorAsset.GetId().IsValid());
  146. bool normalReady = m_normalImage || (!m_configuration.m_macroNormalAsset.GetId().IsValid());
  147. // If we don't have color or normal data, then we don't have *any* useful data, so don't activate the macro material.
  148. bool hasAnyData = m_configuration.m_macroColorAsset.GetId().IsValid() || m_configuration.m_macroNormalAsset.GetId().IsValid();
  149. bool wasPreviouslyActive = m_macroMaterialActive;
  150. bool isNowActive = colorReady && normalReady && hasAnyData;
  151. // Set our state to active or inactive, based on whether or not the macro material instance is now valid.
  152. m_macroMaterialActive = isNowActive;
  153. // Handle the different inactive/active transition possibilities.
  154. if (!wasPreviouslyActive && !isNowActive)
  155. {
  156. // Do nothing, we haven't yet successfully loaded a valid material.
  157. }
  158. else if (!wasPreviouslyActive && isNowActive)
  159. {
  160. // We've transitioned from inactive to active, so send out a message saying that we've been created and start tracking the
  161. // overall shape bounds.
  162. // Get the current shape bounds.
  163. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  164. m_cachedShapeBounds, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
  165. // Start listening for terrain macro material requests.
  166. TerrainMacroMaterialRequestBus::Handler::BusConnect(GetEntityId());
  167. // Start listening for shape changes.
  168. LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId());
  169. // Start listening for macro material modifications
  170. AzFramework::PaintBrushNotificationBus::Handler::BusConnect({ GetEntityId(), GetId() });
  171. TerrainMacroColorModificationBus::Handler::BusConnect(GetEntityId());
  172. MacroMaterialData material = GetTerrainMacroMaterialData();
  173. TerrainMacroMaterialNotificationBus::Broadcast(
  174. &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialCreated, GetEntityId(), material);
  175. NotifyTerrainSystemOfColorChange(m_cachedShapeBounds);
  176. }
  177. else if (wasPreviouslyActive && !isNowActive)
  178. {
  179. // Stop listening to macro material requests or shape changes, and send out a notification that we no longer have a valid
  180. // macro material.
  181. NotifyTerrainSystemOfColorChange(m_cachedShapeBounds);
  182. DestroyMacroColorImageModificationBuffer();
  183. TerrainMacroColorModificationBus::Handler::BusDisconnect();
  184. AzFramework::PaintBrushNotificationBus::Handler::BusDisconnect();
  185. TerrainMacroMaterialRequestBus::Handler::BusDisconnect();
  186. LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
  187. m_cachedShapeBounds = AZ::Aabb::CreateNull();
  188. TerrainMacroMaterialNotificationBus::Broadcast(
  189. &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialDestroyed, GetEntityId());
  190. }
  191. else
  192. {
  193. // We were active both before and after, so just send out a material changed event.
  194. MacroMaterialData material = GetTerrainMacroMaterialData();
  195. TerrainMacroMaterialNotificationBus::Broadcast(
  196. &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialChanged, GetEntityId(), material);
  197. NotifyTerrainSystemOfColorChange(m_cachedShapeBounds);
  198. }
  199. }
  200. void TerrainMacroMaterialComponent::NotifyTerrainSystemOfColorChange(const AZ::Aabb& bounds)
  201. {
  202. // Given an AABB, notify the terrain system that the macro color data has changed within these bounds.
  203. // We also clamp the bounds to the existing Terrain AABB and set the min/max height range to the full Terrain AABB range,
  204. // since macro color materials don't really have a height bounds.
  205. AZ::Aabb terrainAabb = AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero());
  206. AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
  207. terrainAabb, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
  208. const AZ::Aabb clampedBounds = bounds.GetClamped(terrainAabb);
  209. const AZ::Aabb dirtyRegion = AZ::Aabb::CreateFromMinMaxValues(
  210. clampedBounds.GetMin().GetX(), clampedBounds.GetMin().GetY(), bounds.GetMin().GetZ(),
  211. clampedBounds.GetMax().GetX(), clampedBounds.GetMax().GetY(), bounds.GetMax().GetZ());
  212. TerrainSystemServiceRequestBus::Broadcast(
  213. &TerrainSystemServiceRequestBus::Events::RefreshRegion,
  214. dirtyRegion,
  215. AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::ColorData);
  216. }
  217. void TerrainMacroMaterialComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
  218. {
  219. if (asset.GetId() == m_configuration.m_macroColorAsset.GetId())
  220. {
  221. m_configuration.m_macroColorAsset = asset;
  222. m_colorImage = AZ::RPI::StreamingImage::FindOrCreate(m_configuration.m_macroColorAsset);
  223. m_colorImage->GetRHIImage()->SetName(AZ::Name(m_configuration.m_macroColorAsset.GetHint()));
  224. // If we're changing the image data while we're in the middle of modifications,
  225. // refresh the modification buffer with the new data.
  226. if (MacroColorModificationBufferIsActive())
  227. {
  228. DestroyMacroColorImageModificationBuffer();
  229. CreateMacroColorImageModificationBuffer();
  230. }
  231. }
  232. else if (asset.GetId() == m_configuration.m_macroNormalAsset.GetId())
  233. {
  234. m_configuration.m_macroNormalAsset = asset;
  235. m_normalImage = AZ::RPI::StreamingImage::FindOrCreate(m_configuration.m_macroNormalAsset);
  236. m_normalImage->GetRHIImage()->SetName(AZ::Name(m_configuration.m_macroNormalAsset.GetHint()));
  237. }
  238. HandleMaterialStateChange();
  239. }
  240. void TerrainMacroMaterialComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
  241. {
  242. // Update our loaded asset state.
  243. OnAssetReady(asset);
  244. }
  245. MacroMaterialData TerrainMacroMaterialComponent::GetTerrainMacroMaterialData()
  246. {
  247. MacroMaterialData macroMaterial;
  248. macroMaterial.m_entityId = GetEntityId();
  249. macroMaterial.m_bounds = m_cachedShapeBounds;
  250. macroMaterial.m_colorImage = m_colorImage;
  251. macroMaterial.m_normalImage = m_normalImage;
  252. macroMaterial.m_normalFactor = m_configuration.m_normalFactor;
  253. macroMaterial.m_normalFlipX = m_configuration.m_normalFlipX;
  254. macroMaterial.m_normalFlipY = m_configuration.m_normalFlipY;
  255. macroMaterial.m_priority = m_configuration.m_priority;
  256. return macroMaterial;
  257. }
  258. AZ::RHI::Size TerrainMacroMaterialComponent::GetMacroColorImageSize() const
  259. {
  260. if (m_colorImage)
  261. {
  262. const AZ::RHI::ImageDescriptor& imageDescriptor = m_colorImage->GetDescriptor();
  263. return imageDescriptor.m_size;
  264. }
  265. return { 0, 0, 0 };
  266. }
  267. AZ::Vector2 TerrainMacroMaterialComponent::GetMacroColorImagePixelsPerMeter() const
  268. {
  269. if (m_colorImage)
  270. {
  271. const AZ::RHI::ImageDescriptor& imageDescriptor = m_colorImage->GetDescriptor();
  272. return AZ::Vector2(
  273. aznumeric_cast<float>(imageDescriptor.m_size.m_width) / m_cachedShapeBounds.GetXExtent(),
  274. aznumeric_cast<float>(imageDescriptor.m_size.m_height) / m_cachedShapeBounds.GetYExtent());
  275. }
  276. return AZ::Vector2::CreateZero();
  277. }
  278. AZ::Data::Asset<AZ::RPI::StreamingImageAsset> TerrainMacroMaterialComponent::GetMacroColorAsset() const
  279. {
  280. return m_configuration.m_macroColorAsset;
  281. }
  282. void TerrainMacroMaterialComponent::SetMacroColorAsset(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& asset)
  283. {
  284. // If we're setting the component to the same asset we're already using, then early-out.
  285. if (asset.GetId() == m_configuration.m_macroColorAsset.GetId())
  286. {
  287. return;
  288. }
  289. // Stop listening for the current image asset.
  290. AZ::Data::AssetBus::MultiHandler::BusDisconnect(m_configuration.m_macroColorAsset.GetId());
  291. m_configuration.m_macroColorAsset = asset;
  292. // If we're using a modification buffer, we want to keep it active until the new image has finished loading in, so
  293. // we won't destroy or recreate it here.
  294. // If the new asset is valid, try to load it.
  295. if (m_configuration.m_macroColorAsset.GetId().IsValid())
  296. {
  297. // If we have a valid Asset ID, check to see if it also appears in the AssetCatalog. This might be an Asset ID for an asset
  298. // that doesn't exist yet if it was just created from the Editor component.
  299. AZ::Data::AssetInfo assetInfo;
  300. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  301. assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, m_configuration.m_macroColorAsset.GetId());
  302. // Only queue the load if it appears in the Asset Catalog. If it doesn't, we'll get notified when it shows up.
  303. if (assetInfo.m_assetId.IsValid())
  304. {
  305. m_configuration.m_macroColorAsset.QueueLoad(
  306. AZ::Data::AssetLoadParameters(nullptr, AZ::Data::AssetDependencyLoadRules::LoadAll));
  307. }
  308. // Start listening for all events for this asset.
  309. AZ::Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_macroColorAsset.GetId());
  310. }
  311. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  312. }
  313. void TerrainMacroMaterialComponent::OnPaintModeBegin()
  314. {
  315. StartMacroColorImageModification();
  316. }
  317. void TerrainMacroMaterialComponent::OnPaintModeEnd()
  318. {
  319. EndMacroColorImageModification();
  320. }
  321. AZ::Color TerrainMacroMaterialComponent::OnGetColor(const AZ::Vector3& brushCenter) const
  322. {
  323. AZ::Color color(0.0f, 0.0f, 0.0f, 1.0f);
  324. GetMacroColorPixelValuesByPosition(AZStd::span<const AZ::Vector3>(&brushCenter, 1), AZStd::span<AZ::Color>(&color, 1));
  325. return color;
  326. }
  327. void TerrainMacroMaterialComponent::StartMacroColorImageModification()
  328. {
  329. if (!m_macroColorImageModifier)
  330. {
  331. AZ_Assert(
  332. m_numImageModificationsActive == 0,
  333. "The imageModifier should exist since image modifications are already currently active.");
  334. m_macroColorImageModifier = AZStd::make_unique<MacroMaterialImageModifier>(AZ::EntityComponentIdPair(GetEntityId(), GetId()));
  335. }
  336. if (m_modifiedMacroColorImageData.empty())
  337. {
  338. CreateMacroColorImageModificationBuffer();
  339. }
  340. m_numImageModificationsActive++;
  341. }
  342. void TerrainMacroMaterialComponent::EndMacroColorImageModification()
  343. {
  344. AZ_Assert(m_numImageModificationsActive > 0, "Mismatched calls to StartMacroColorImageModification / EndMacroColorImageModification");
  345. m_numImageModificationsActive--;
  346. if (m_numImageModificationsActive == 0)
  347. {
  348. m_macroColorImageModifier = {};
  349. }
  350. }
  351. void TerrainMacroMaterialComponent::StartMacroColorPixelModifications()
  352. {
  353. AZ_Assert(!m_modifyingPixels, "Called StartMacroColorPixelModifications twice without calling EndMacroColorPixelModifications");
  354. m_modifyingPixels = true;
  355. // We'll use these to track the subregion of pixels that have been modified.
  356. m_leftTopPixel = PixelIndex(AZStd::numeric_limits<int16_t>::max(), AZStd::numeric_limits<int16_t>::max());
  357. m_rightBottomPixel = PixelIndex(aznumeric_cast<int16_t>(0), aznumeric_cast<int16_t>(0));
  358. }
  359. void TerrainMacroMaterialComponent::EndMacroColorPixelModifications()
  360. {
  361. if (m_modifyingPixels)
  362. {
  363. UpdateMacroMaterialTexture(m_leftTopPixel, m_rightBottomPixel);
  364. }
  365. m_modifyingPixels = false;
  366. }
  367. AZStd::span<const uint32_t> TerrainMacroMaterialComponent::GetMacroColorImageModificationBuffer() const
  368. {
  369. // This isn't generally safe to do, but this is a protected method only exposed outward to the editor component so that
  370. // we have a way to save the modification image buffer as a new source asset.
  371. // This method shouldn't get called by anything else.
  372. return AZStd::span<const uint32_t>(m_modifiedMacroColorImageData.data(), m_modifiedMacroColorImageData.size());
  373. }
  374. bool TerrainMacroMaterialComponent::MacroColorImageIsModified() const
  375. {
  376. return !m_modifiedMacroColorImageData.empty() && m_macroColorImageIsModified;
  377. }
  378. uint32_t TerrainMacroMaterialComponent::GetHighestLoadedMipLevel() const
  379. {
  380. if (m_configuration.m_macroColorAsset->IsReady())
  381. {
  382. // Walk through the mip chains to find the highest loaded mip level.
  383. const size_t numMipChains = m_configuration.m_macroColorAsset->GetMipChainCount();
  384. if (numMipChains > 0)
  385. {
  386. // Check the standard mip chain assets to look for the highest loaded mip.
  387. for (size_t mipChainIndex = 0; mipChainIndex < (numMipChains - 1); mipChainIndex++)
  388. {
  389. auto mipChainAsset = m_configuration.m_macroColorAsset->GetMipChainAsset(mipChainIndex);
  390. if (mipChainAsset->IsReady())
  391. {
  392. return aznumeric_cast<uint32_t>(m_configuration.m_macroColorAsset->GetMipLevel(mipChainIndex));
  393. }
  394. }
  395. // The tail mip chain is always loaded but can't be queried the same way as the other mip chain assets,
  396. // so we'll just assume that it's loaded and return its highest mip level.
  397. // Note that this could still potentially be mip 0 if the image is extremely small.
  398. return aznumeric_cast<uint32_t>(m_configuration.m_macroColorAsset->GetMipLevel(numMipChains - 1));
  399. }
  400. }
  401. // No loaded mip level found.
  402. return InvalidMipLevel;
  403. }
  404. void TerrainMacroMaterialComponent::CreateMacroColorImageModificationBuffer()
  405. {
  406. // Get the highest loaded mip, which is hopefully mip 0.
  407. uint32_t mipLevel = GetHighestLoadedMipLevel();
  408. if (mipLevel == InvalidMipLevel)
  409. {
  410. AZ_Error("TerrainMacroMaterialComponent", false, "No mip levels are loaded, cannot create an image modification buffer.");
  411. return;
  412. }
  413. if (mipLevel > 0)
  414. {
  415. AZ_Warning("TerrainMacroMaterialComponent", false,
  416. "Highest mip level loaded is %zu, painting data will be of lesser quality.", mipLevel);
  417. }
  418. const AZ::RHI::ImageDescriptor& imageDescriptor = m_configuration.m_macroColorAsset->GetImageDescriptor();
  419. auto imageData = m_configuration.m_macroColorAsset->GetSubImageData(mipLevel, 0);
  420. const auto& width = imageDescriptor.m_size.m_width;
  421. const auto& height = imageDescriptor.m_size.m_height;
  422. // Track that the image hasn't been modified yet, even though we've created a modification buffer.
  423. m_macroColorImageIsModified = false;
  424. if (m_modifiedMacroColorImageData.empty())
  425. {
  426. // Create a memory buffer for holding all of our modified image information.
  427. // We use a buffer of uint32 colors (R8G8B8A8) so that it doesn't get overly large when modifying large textures.
  428. m_modifiedMacroColorImageData.reserve(width * height);
  429. // Fill the buffer with all of our existing pixel values.
  430. for (uint32_t y = 0; y < height; y++)
  431. {
  432. for (uint32_t x = 0; x < width; x++)
  433. {
  434. AZ::Color pixel = AZ::RPI::GetImageDataPixelValue<AZ::Color>(imageData, imageDescriptor, x, y);
  435. m_modifiedMacroColorImageData.emplace_back(pixel.ToU32());
  436. }
  437. }
  438. // Create an image descriptor describing our new buffer (correct width, height, and 8-bit RGB channels)
  439. auto modifiedImageDescriptor =
  440. AZ::RHI::ImageDescriptor::Create2D(AZ::RHI::ImageBindFlags::ShaderRead, width, height, AZ::RHI::Format::R8G8B8A8_UNORM);
  441. // Set our imageData pointer to point to our modified data buffer.
  442. auto modifiedImageData = AZStd::span<const uint8_t>(
  443. reinterpret_cast<uint8_t*>(m_modifiedMacroColorImageData.data()), m_modifiedMacroColorImageData.size() * sizeof(uint32_t));
  444. // Create the initial buffer for the downloaded color data
  445. const AZ::Data::Instance<AZ::RPI::AttachmentImagePool> imagePool =
  446. AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
  447. const AZ::Name ModificationImageName = AZ::Name("ModifiedMacroColorImage");
  448. m_colorImage =
  449. AZ::RPI::AttachmentImage::Create(*imagePool.get(), modifiedImageDescriptor, ModificationImageName, nullptr, nullptr);
  450. AZ_Error("Terrain", m_colorImage, "Failed to initialize the modification image buffer.");
  451. // Push our initialized pixel data up to the GPU.
  452. UpdateMacroMaterialTexture(
  453. PixelIndex(aznumeric_cast<int16_t>(0), aznumeric_cast<int16_t>(0)),
  454. PixelIndex(aznumeric_cast<int16_t>(width - 1), aznumeric_cast<int16_t>(height - 1)));
  455. // Notify that the material has changed.
  456. MacroMaterialData material = GetTerrainMacroMaterialData();
  457. TerrainMacroMaterialNotificationBus::Broadcast(
  458. &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialChanged, GetEntityId(), material);
  459. }
  460. else
  461. {
  462. // If this triggers, we've somehow gotten our image modification buffer out of sync with the image descriptor information.
  463. AZ_Assert(m_modifiedMacroColorImageData.size() == (width * height), "Image modification buffer exists but is the wrong size.");
  464. }
  465. }
  466. void TerrainMacroMaterialComponent::DestroyMacroColorImageModificationBuffer()
  467. {
  468. m_modifiedMacroColorImageData.resize(0);
  469. m_macroColorImageIsModified = false;
  470. }
  471. bool TerrainMacroMaterialComponent::MacroColorModificationBufferIsActive() const
  472. {
  473. return (!m_modifiedMacroColorImageData.empty());
  474. }
  475. void TerrainMacroMaterialComponent::UpdateMacroMaterialTexture(
  476. PixelIndex leftTopPixel, PixelIndex rightBottomPixel)
  477. {
  478. // If these are still in their unitialized state, we've got nothing to update.
  479. if ((rightBottomPixel.first < leftTopPixel.first) || (rightBottomPixel.second < leftTopPixel.second))
  480. {
  481. return;
  482. }
  483. const AZ::RHI::ImageDescriptor& imageDescriptor = m_colorImage->GetDescriptor();
  484. const auto& imageWidth = imageDescriptor.m_size.m_width;
  485. const auto& imageHeight = imageDescriptor.m_size.m_height;
  486. // When updating an image, the new data we provide needs to be a contiguous block of pixels, so we can't
  487. // just directly use our modification buffer unless we update the entire image. Instead, we'll need to create
  488. // a new temporary buffer containing just the subset of pixels that we want to update.
  489. // However, there's a performance tradeoff between creating a temporary buffer that updates less pixels and using
  490. // our full buffer as-is but updating all of the pixels.
  491. // To deal with this tradeoff, we'll use the heuristic that if less than 50% of the total pixels have changed, we'll
  492. // create the temporary buffer. If 50% or more have changed, we'll just upload the whole image.
  493. // By default, we'll update the entire image directly from our modification buffer.
  494. uint32_t updateLeftPixel = 0;
  495. uint32_t updateTopPixel = 0;
  496. uint32_t updateWidth = imageWidth;
  497. uint32_t updateHeight = imageHeight;
  498. void* sourceData = m_modifiedMacroColorImageData.data();
  499. uint32_t modifiedWidth = rightBottomPixel.first - leftTopPixel.first + 1;
  500. uint32_t modifiedHeight = rightBottomPixel.second - leftTopPixel.second + 1;
  501. AZStd::vector<uint32_t> tempBuffer;
  502. // If less than 50% of the total pixels have changed, create a new temporary buffer instead that only contains our
  503. // modified pixel region.
  504. constexpr float TotalPixelsChangedPercent = 0.50f;
  505. if (((modifiedWidth * modifiedHeight) / (imageWidth * imageHeight)) <= TotalPixelsChangedPercent)
  506. {
  507. updateLeftPixel = leftTopPixel.first;
  508. updateTopPixel = leftTopPixel.second;
  509. updateWidth = modifiedWidth;
  510. updateHeight = modifiedHeight;
  511. tempBuffer.resize_no_construct(updateWidth * updateHeight);
  512. for (uint32_t y = 0; y < updateHeight; y++)
  513. {
  514. memcpy(
  515. &tempBuffer[(y * updateWidth)],
  516. &m_modifiedMacroColorImageData[((y + leftTopPixel.second) * imageWidth) + leftTopPixel.first],
  517. updateWidth * sizeof(uint32_t));
  518. }
  519. sourceData = tempBuffer.data();
  520. }
  521. // Upload the image changes to the GPU.
  522. const uint32_t BytesPerPixel = 4;
  523. AZ::RHI::ImageUpdateRequest imageUpdateRequest;
  524. imageUpdateRequest.m_imageSubresourcePixelOffset.m_left = updateLeftPixel;
  525. imageUpdateRequest.m_imageSubresourcePixelOffset.m_top = updateTopPixel;
  526. AZ::RHI::DeviceImageSubresourceLayout layout{{updateWidth, updateHeight, 1}, updateHeight, updateWidth * BytesPerPixel, updateWidth * updateHeight * BytesPerPixel, 1, 1};
  527. imageUpdateRequest.m_sourceSubresourceLayout.Init(m_colorImage->GetRHIImage()->GetDeviceMask(), layout);
  528. imageUpdateRequest.m_sourceData = sourceData;
  529. imageUpdateRequest.m_image = m_colorImage->GetRHIImage();
  530. m_colorImage->UpdateImageContents(imageUpdateRequest);
  531. }
  532. void TerrainMacroMaterialComponent::GetMacroColorPixelValuesByPosition(
  533. AZStd::span<const AZ::Vector3> positions, AZStd::span<AZ::Color> outValues) const
  534. {
  535. AZ_Assert(!m_modifiedMacroColorImageData.empty(), "Pixel values are only available during modifications.");
  536. const AZ::RHI::ImageDescriptor& imageDescriptor = m_colorImage->GetDescriptor();
  537. const auto& width = imageDescriptor.m_size.m_width;
  538. const auto& height = imageDescriptor.m_size.m_height;
  539. for (size_t index = 0; index < positions.size(); index++)
  540. {
  541. auto pixelX = AZ::Lerp(
  542. 0.0f,
  543. aznumeric_cast<float>(width),
  544. (positions[index].GetX() - m_cachedShapeBounds.GetMin().GetX()) / m_cachedShapeBounds.GetXExtent());
  545. auto pixelY = AZ::Lerp(
  546. 0.0f,
  547. aznumeric_cast<float>(height),
  548. (positions[index].GetY() - m_cachedShapeBounds.GetMin().GetY()) / m_cachedShapeBounds.GetYExtent());
  549. if ((pixelX >= 0.0f) && (pixelY >= 0.0f) && (pixelX <= width) && (pixelY <= height))
  550. {
  551. auto x = AZStd::clamp(aznumeric_cast<AZ::u32>(pixelX), aznumeric_cast<AZ::u32>(0), width - 1);
  552. auto y = AZStd::clamp(aznumeric_cast<AZ::u32>(pixelY), aznumeric_cast<AZ::u32>(0), height - 1);
  553. // Flip the y because images are stored in reverse of our world axes
  554. y = (height - 1) - y;
  555. uint8_t r = (m_modifiedMacroColorImageData[(y * width) + x] >> 0) & 0xFF;
  556. uint8_t g = (m_modifiedMacroColorImageData[(y * width) + x] >> 8) & 0xFF;
  557. uint8_t b = (m_modifiedMacroColorImageData[(y * width) + x] >> 16) & 0xFF;
  558. uint8_t a = (m_modifiedMacroColorImageData[(y * width) + x] >> 24) & 0xFF;
  559. outValues[index] = AZ::Color(r, g, b, a);
  560. }
  561. else
  562. {
  563. outValues[index] = AZ::Color(0.0f, 0.0f, 0.0f, 1.0f);
  564. }
  565. }
  566. }
  567. void TerrainMacroMaterialComponent::GetMacroColorPixelIndicesForPositions(
  568. AZStd::span<const AZ::Vector3> positions, AZStd::span<PixelIndex> outIndices) const
  569. {
  570. const AZ::RHI::ImageDescriptor& imageDescriptor = m_colorImage->GetDescriptor();
  571. const auto& width = imageDescriptor.m_size.m_width;
  572. const auto& height = imageDescriptor.m_size.m_height;
  573. for (size_t index = 0; index < positions.size(); index++)
  574. {
  575. auto pixelX = AZ::Lerp(
  576. 0.0f,
  577. aznumeric_cast<float>(width),
  578. (positions[index].GetX() - m_cachedShapeBounds.GetMin().GetX()) / m_cachedShapeBounds.GetXExtent());
  579. auto pixelY = AZ::Lerp(
  580. 0.0f,
  581. aznumeric_cast<float>(height),
  582. (positions[index].GetY() - m_cachedShapeBounds.GetMin().GetY()) / m_cachedShapeBounds.GetYExtent());
  583. if ((pixelX >= 0.0f) && (pixelY >= 0.0f) && (pixelX <= width) && (pixelY <= height))
  584. {
  585. auto x = AZStd::clamp(aznumeric_cast<AZ::u32>(pixelX), aznumeric_cast<AZ::u32>(0), width - 1);
  586. auto y = AZStd::clamp(aznumeric_cast<AZ::u32>(pixelY), aznumeric_cast<AZ::u32>(0), height - 1);
  587. // Flip the y because images are stored in reverse of our world axes
  588. y = (height - 1) - y;
  589. outIndices[index] = PixelIndex(aznumeric_cast<int16_t>(x), aznumeric_cast<int16_t>(y));
  590. }
  591. else
  592. {
  593. outIndices[index] = PixelIndex(aznumeric_cast<int16_t>(-1), aznumeric_cast<int16_t>(-1));
  594. }
  595. }
  596. }
  597. void TerrainMacroMaterialComponent::GetMacroColorPixelValuesByPixelIndex(
  598. AZStd::span<const PixelIndex> positions, AZStd::span<AZ::Color> outValues) const
  599. {
  600. AZ_Assert(!m_modifiedMacroColorImageData.empty(), "Pixel values are only available during modifications.");
  601. const AZ::RHI::ImageDescriptor& imageDescriptor = m_colorImage->GetDescriptor();
  602. const auto& width = imageDescriptor.m_size.m_width;
  603. const auto& height = imageDescriptor.m_size.m_height;
  604. for (size_t index = 0; index < positions.size(); index++)
  605. {
  606. const auto& [x, y] = positions[index];
  607. if ((x >= 0) && (x < aznumeric_cast<int16_t>(width)) && (y >= 0) && (y < aznumeric_cast<int16_t>(height)))
  608. {
  609. uint8_t r = (m_modifiedMacroColorImageData[(y * width) + x] >> 0) & 0xFF;
  610. uint8_t g = (m_modifiedMacroColorImageData[(y * width) + x] >> 8) & 0xFF;
  611. uint8_t b = (m_modifiedMacroColorImageData[(y * width) + x] >> 16) & 0xFF;
  612. uint8_t a = (m_modifiedMacroColorImageData[(y * width) + x] >> 24) & 0xFF;
  613. outValues[index] = AZ::Color(r, g, b, a);
  614. }
  615. }
  616. }
  617. void TerrainMacroMaterialComponent::SetMacroColorPixelValuesByPixelIndex(
  618. AZStd::span<const PixelIndex> positions, AZStd::span<const AZ::Color> values)
  619. {
  620. if (m_modifiedMacroColorImageData.empty())
  621. {
  622. AZ_Error("TerrainMacroMaterialComponent", false,
  623. "Image modification mode needs to be started before the image values can be set.");
  624. return;
  625. }
  626. const AZ::RHI::ImageDescriptor& imageDescriptor = m_colorImage->GetDescriptor();
  627. const auto& width = imageDescriptor.m_size.m_width;
  628. const auto& height = imageDescriptor.m_size.m_height;
  629. // No pixels, so nothing to modify.
  630. if ((width == 0) || (height == 0))
  631. {
  632. return;
  633. }
  634. for (size_t index = 0; index < positions.size(); index++)
  635. {
  636. const auto& [x, y] = positions[index];
  637. if ((x >= 0) && (x < aznumeric_cast<int16_t>(width)) && (y >= 0) && (y < aznumeric_cast<int16_t>(height)))
  638. {
  639. // Modify the correct pixel in our modification buffer.
  640. m_modifiedMacroColorImageData[(y * width) + x] = values[index].ToU32();
  641. // Update the range of pixels that we've modified so that we know what subregion to upload to the GPU
  642. // once we're finished modifying the pixels.
  643. m_leftTopPixel.first = AZStd::min(m_leftTopPixel.first, x);
  644. m_rightBottomPixel.first = AZStd::max(m_rightBottomPixel.first, x);
  645. m_leftTopPixel.second = AZStd::min(m_leftTopPixel.second, y);
  646. m_rightBottomPixel.second = AZStd::max(m_rightBottomPixel.second, y);
  647. // Track that we've modified the image
  648. m_macroColorImageIsModified = true;
  649. }
  650. }
  651. }
  652. } // namespace Terrain