ImageGradientComponent.cpp 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321
  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 <GradientSignal/Components/ImageGradientComponent.h>
  9. #include <Atom/ImageProcessing/ImageProcessingDefines.h>
  10. #include <Atom/RPI.Public/RPIUtils.h>
  11. #include <AzCore/Asset/AssetManager.h>
  12. #include <AzCore/Asset/AssetSerializer.h>
  13. #include <AzCore/Debug/Profiler.h>
  14. #include <AzCore/Math/MathUtils.h>
  15. #include <AzCore/RTTI/BehaviorContext.h>
  16. #include <AzCore/Serialization/EditContext.h>
  17. #include <AzCore/Serialization/SerializeContext.h>
  18. #include <GradientSignal/Ebuses/GradientTransformRequestBus.h>
  19. #include <LmbrCentral/Dependency/DependencyMonitor.h>
  20. namespace GradientSignal
  21. {
  22. AZ::JsonSerializationResult::Result JsonImageGradientConfigSerializer::Load(
  23. void* outputValue, [[maybe_unused]] const AZ::Uuid& outputValueTypeId,
  24. const rapidjson::Value& inputValue, AZ::JsonDeserializerContext& context)
  25. {
  26. namespace JSR = AZ::JsonSerializationResult;
  27. auto configInstance = reinterpret_cast<ImageGradientConfig*>(outputValue);
  28. AZ_Assert(configInstance, "Output value for JsonImageGradientConfigSerializer can't be null.");
  29. JSR::ResultCode result(JSR::Tasks::ReadField);
  30. // The tiling field was moved from individual float values for X/Y to an AZ::Vector2,
  31. // so we need to handle migrating these float fields over to the vector field
  32. rapidjson::Value::ConstMemberIterator tilingXIter = inputValue.FindMember("TilingX");
  33. if (tilingXIter != inputValue.MemberEnd())
  34. {
  35. AZ::ScopedContextPath subPath(context, "TilingX");
  36. float tilingX;
  37. result.Combine(ContinueLoading(&tilingX, azrtti_typeid<float>(), tilingXIter->value, context));
  38. configInstance->m_tiling.SetX(tilingX);
  39. }
  40. rapidjson::Value::ConstMemberIterator tilingYIter = inputValue.FindMember("TilingY");
  41. if (tilingYIter != inputValue.MemberEnd())
  42. {
  43. AZ::ScopedContextPath subPath(context, "TilingY");
  44. float tilingY;
  45. result.Combine(ContinueLoading(&tilingY, azrtti_typeid<float>(), tilingYIter->value, context));
  46. configInstance->m_tiling.SetY(tilingY);
  47. }
  48. // We can distinguish between version 1 and 2 by the presence of the "ImageAsset" field,
  49. // which is only in version 1.
  50. // For version 2, we don't need to do any special processing, so just let the base class
  51. // load the JSON if we don't find the "ImageAsset" field.
  52. rapidjson::Value::ConstMemberIterator itr = inputValue.FindMember("ImageAsset");
  53. if (itr == inputValue.MemberEnd())
  54. {
  55. return AZ::BaseJsonSerializer::Load(outputValue, outputValueTypeId, inputValue, context);
  56. }
  57. // Version 1 stored a custom GradientSignal::ImageAsset as the image asset.
  58. // In Version 2, we changed the image asset to use the generic AZ::RPI::StreamingImageAsset,
  59. // so they are both AZ::Data::Asset but reference different types.
  60. // Using the assetHint, which will be something like "my_test_image.gradimage",
  61. // we need to find the valid streaming image asset product from the same source,
  62. // which will be something like "my_test_image.png.streamingimage"
  63. AZStd::string assetHint;
  64. AZ::Data::AssetId fixedAssetId;
  65. auto it = itr->value.FindMember("assetHint");
  66. if (it != itr->value.MemberEnd())
  67. {
  68. AZ::ScopedContextPath subPath(context, "assetHint");
  69. result.Combine(ContinueLoading(&assetHint, azrtti_typeid<AZStd::string>(), it->value, context));
  70. if (assetHint.ends_with(".gradimage"))
  71. {
  72. // We don't know what image format the original source was, so we need to loop through
  73. // all the supported image extensions to check if they have a valid corresponding
  74. // streaming image asset
  75. for (auto& supportedImageExtension : ImageProcessingAtom::s_SupportedImageExtensions)
  76. {
  77. AZStd::string imageExtension(supportedImageExtension);
  78. // The image extensions are stored with a wildcard (e.g. *.png) so we need to strip that off first
  79. AZ::StringFunc::Replace(imageExtension, "*", "");
  80. // Form potential streaming image path (e.g. my_test_image.png.streamingimage)
  81. AZStd::string potentialStreamingImagePath(assetHint);
  82. AZ::StringFunc::Replace(potentialStreamingImagePath, ".gradimage", "");
  83. potentialStreamingImagePath += imageExtension + ".streamingimage";
  84. // Check if there is a valid streaming image asset for this path
  85. AZ::Data::AssetCatalogRequestBus::BroadcastResult(fixedAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, potentialStreamingImagePath.c_str(), azrtti_typeid<AZ::Data::Asset<AZ::RPI::StreamingImageAsset>>(), false);
  86. if (fixedAssetId.IsValid())
  87. {
  88. break;
  89. }
  90. }
  91. }
  92. }
  93. // The "AdvancedMode" toggle has been removed, all settings are always active and visible now.
  94. // If the "AdvancedMode" setting was previously disabled, make sure to set the appropriate settings to their defaults
  95. rapidjson::Value::ConstMemberIterator advancedModeIter = inputValue.FindMember("AdvancedMode");
  96. if (advancedModeIter != inputValue.MemberEnd())
  97. {
  98. AZ::ScopedContextPath subPath(context, "AdvancedMode");
  99. bool advancedMode = false;
  100. result.Combine(ContinueLoading(&advancedMode, azrtti_typeid<bool>(), advancedModeIter->value, context));
  101. if (!advancedMode)
  102. {
  103. configInstance->m_channelToUse = ChannelToUse::Red;
  104. configInstance->m_customScaleType = CustomScaleType::None;
  105. configInstance->m_mipIndex = 0;
  106. configInstance->m_samplingType = SamplingType::Point;
  107. }
  108. }
  109. // Replace the old gradimage with new AssetId for streaming image asset
  110. if (fixedAssetId.IsValid())
  111. {
  112. configInstance->m_imageAsset = AZ::Data::AssetManager::Instance().GetAsset<AZ::RPI::StreamingImageAsset>(fixedAssetId, AZ::Data::AssetLoadBehavior::QueueLoad);
  113. }
  114. return context.Report(result,
  115. result.GetProcessing() != JSR::Processing::Halted ?
  116. "Successfully loaded ImageGradientConfig information." :
  117. "Failed to load ImageGradientConfig information.");
  118. }
  119. AZ_CLASS_ALLOCATOR_IMPL(JsonImageGradientConfigSerializer, AZ::SystemAllocator);
  120. bool DoesFormatSupportTerrarium(AZ::RHI::Format format)
  121. {
  122. // The terrarium type is only supported by 8-bit formats that have
  123. // at least RGB
  124. switch (format)
  125. {
  126. case AZ::RHI::Format::R8G8B8A8_UNORM:
  127. case AZ::RHI::Format::R8G8B8A8_UNORM_SRGB:
  128. return true;
  129. }
  130. return false;
  131. }
  132. void ImageGradientConfig::Reflect(AZ::ReflectContext* context)
  133. {
  134. if (auto jsonContext = azrtti_cast<AZ::JsonRegistrationContext*>(context))
  135. {
  136. jsonContext->Serializer<JsonImageGradientConfigSerializer>()->HandlesType<ImageGradientConfig>();
  137. }
  138. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  139. if (serialize)
  140. {
  141. serialize->Class<ImageGradientConfig, AZ::ComponentConfig>()
  142. ->Version(6)
  143. ->Field("StreamingImageAsset", &ImageGradientConfig::m_imageAsset)
  144. ->Field("SamplingType", &ImageGradientConfig::m_samplingType)
  145. ->Field("Tiling", &ImageGradientConfig::m_tiling)
  146. ->Field("ChannelToUse", &ImageGradientConfig::m_channelToUse)
  147. ->Field("MipIndex", &ImageGradientConfig::m_mipIndex)
  148. ->Field("CustomScale", &ImageGradientConfig::m_customScaleType)
  149. ->Field("ScaleRangeMin", &ImageGradientConfig::m_scaleRangeMin)
  150. ->Field("ScaleRangeMax", &ImageGradientConfig::m_scaleRangeMax)
  151. ;
  152. }
  153. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  154. {
  155. behaviorContext->Class<ImageGradientConfig>()
  156. ->Constructor()
  157. ->Attribute(AZ::Script::Attributes::Category, "Vegetation")
  158. ->Property("tiling", BehaviorValueProperty(&ImageGradientConfig::m_tiling))
  159. ;
  160. }
  161. }
  162. bool ImageGradientConfig::GetManualScaleVisibility() const
  163. {
  164. return (m_customScaleType == CustomScaleType::Manual);
  165. }
  166. bool ImageGradientConfig::IsImageAssetReadOnly() const
  167. {
  168. return m_numImageModificationsActive > 0;
  169. }
  170. bool ImageGradientConfig::AreImageOptionsReadOnly() const
  171. {
  172. return (m_numImageModificationsActive > 0) || !(m_imageAsset.GetId().IsValid());
  173. }
  174. AZStd::string ImageGradientConfig::GetImageAssetPropertyName() const
  175. {
  176. return m_imageAssetPropertyLabel;
  177. }
  178. void ImageGradientConfig::SetImageAssetPropertyName(const AZStd::string& imageAssetPropertyName)
  179. {
  180. m_imageAssetPropertyLabel = imageAssetPropertyName;
  181. }
  182. void ImageGradientComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  183. {
  184. services.push_back(AZ_CRC_CE("GradientService"));
  185. }
  186. void ImageGradientComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  187. {
  188. services.push_back(AZ_CRC_CE("GradientService"));
  189. }
  190. void ImageGradientComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  191. {
  192. services.push_back(AZ_CRC_CE("GradientTransformService"));
  193. }
  194. void ImageGradientComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& services)
  195. {
  196. }
  197. void ImageGradientComponent::Reflect(AZ::ReflectContext* context)
  198. {
  199. ImageGradientConfig::Reflect(context);
  200. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  201. if (serialize)
  202. {
  203. serialize->Class<ImageGradientComponent, AZ::Component>()
  204. ->Version(0)
  205. ->Field("Configuration", &ImageGradientComponent::m_configuration)
  206. ;
  207. }
  208. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  209. {
  210. behaviorContext->Constant("ImageGradientComponentTypeId", BehaviorConstant(ImageGradientComponentTypeId));
  211. behaviorContext->Class<ImageGradientComponent>()
  212. ->RequestBus("ImageGradientRequestBus")
  213. ;
  214. behaviorContext->EBus<ImageGradientRequestBus>("ImageGradientRequestBus")
  215. ->Attribute(AZ::Script::Attributes::Category, "Vegetation/ImageGradient")
  216. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  217. ->Attribute(AZ::Script::Attributes::Module, "vegetation")
  218. ->Event("GetImageAssetPath", &ImageGradientRequestBus::Events::GetImageAssetPath)
  219. ->Event("GetImageAssetSourcePath", &ImageGradientRequestBus::Events::GetImageAssetSourcePath)
  220. ->Event("SetImageAssetPath", &ImageGradientRequestBus::Events::SetImageAssetPath)
  221. ->Event("SetImageAssetSourcePath", &ImageGradientRequestBus::Events::SetImageAssetSourcePath)
  222. ->VirtualProperty("ImageAssetPath", "GetImageAssetPath", "SetImageAssetPath")
  223. ->Event("GetTilingX", &ImageGradientRequestBus::Events::GetTilingX)
  224. ->Event("SetTilingX", &ImageGradientRequestBus::Events::SetTilingX)
  225. ->VirtualProperty("TilingX", "GetTilingX", "SetTilingX")
  226. ->Event("GetTilingY", &ImageGradientRequestBus::Events::GetTilingY)
  227. ->Event("SetTilingY", &ImageGradientRequestBus::Events::SetTilingY)
  228. ->VirtualProperty("TilingY", "GetTilingY", "SetTilingY")
  229. ;
  230. }
  231. }
  232. ImageGradientComponent::ImageGradientComponent(const ImageGradientConfig& configuration)
  233. : m_configuration(configuration)
  234. {
  235. }
  236. void ImageGradientComponent::GetSubImageData()
  237. {
  238. if (!m_configuration.m_imageAsset || !m_configuration.m_imageAsset.IsReady())
  239. {
  240. return;
  241. }
  242. // If we have loaded in an old image asset with an unsupported pixel format,
  243. // don't try to access the image data because there will be spam of asserts,
  244. // so just log an error message and bail out
  245. AZ::RHI::Format format = m_configuration.m_imageAsset->GetImageDescriptor().m_format;
  246. bool isFormatSupported = AZ::RPI::IsImageDataPixelAPISupported(format);
  247. if (!isFormatSupported)
  248. {
  249. AZ_Error("GradientSignal", false, "Image asset (%s) has an unsupported pixel format: %s",
  250. m_configuration.m_imageAsset.GetHint().c_str(), AZ::RHI::ToString(format));
  251. return;
  252. }
  253. // Prevent loading of the image data if an invalid configuration was specified by the user
  254. const auto numComponents = AZ::RHI::GetFormatComponentCount(format);
  255. const AZ::u8 channel = aznumeric_cast<AZ::u8>(m_configuration.m_channelToUse);
  256. if (m_configuration.m_channelToUse == ChannelToUse::Terrarium)
  257. {
  258. if (!DoesFormatSupportTerrarium(format))
  259. {
  260. AZ_Error("GradientSignal", false, "Unable to interpret image as Terrarium because image asset (%s) has pixel format (%s), which only supports %d channels",
  261. m_configuration.m_imageAsset.GetHint().c_str(), AZ::RHI::ToString(format), numComponents);
  262. return;
  263. }
  264. }
  265. else if (channel >= numComponents)
  266. {
  267. AZ_Error("GradientSignal", false, "Unable to use channel %d because image asset (%s) has pixel format (%s), which only supports %d channels",
  268. channel, m_configuration.m_imageAsset.GetHint().c_str(), AZ::RHI::ToString(format), numComponents);
  269. return;
  270. }
  271. m_currentChannel = m_configuration.m_channelToUse;
  272. m_currentScaleType = m_configuration.m_customScaleType;
  273. m_currentSamplingType = m_configuration.m_samplingType;
  274. // Make sure the custom mip level doesn't exceed the available mip levels in this
  275. // image asset. If so, then just use the lowest available mip level.
  276. auto mipLevelCount = m_configuration.m_imageAsset->GetImageDescriptor().m_mipLevels;
  277. m_currentMipIndex = m_configuration.m_mipIndex;
  278. if (m_currentMipIndex >= mipLevelCount)
  279. {
  280. AZ_Warning("GradientSignal", false, "Mip level index (%d) out of bounds, only %d levels available. Using lowest available mip level",
  281. m_currentMipIndex, mipLevelCount);
  282. m_currentMipIndex = aznumeric_cast<AZ::u32>(mipLevelCount) - 1;
  283. }
  284. // Update our cached image data
  285. UpdateCachedImageBufferData(
  286. m_configuration.m_imageAsset->GetImageDescriptorForMipLevel(m_currentMipIndex),
  287. m_configuration.m_imageAsset->GetSubImageData(m_currentMipIndex, 0));
  288. // Calculate the multiplier and offset based on our scale type
  289. // Make sure we do this last, because the calculation might
  290. // depend on the image data (e.g. auto scale finds the min/max value
  291. // from the image data, which might be different based on the mip level)
  292. switch (m_currentScaleType)
  293. {
  294. case CustomScaleType::Auto:
  295. SetupAutoScaleMultiplierAndOffset();
  296. break;
  297. case CustomScaleType::Manual:
  298. SetupManualScaleMultiplierAndOffset();
  299. break;
  300. case CustomScaleType::None:
  301. default:
  302. SetupDefaultMultiplierAndOffset();
  303. break;
  304. }
  305. }
  306. float ImageGradientComponent::GetValueFromImageData(SamplingType samplingType, const AZ::Vector3& uvw, float defaultValue) const
  307. {
  308. if (!m_imageData.empty())
  309. {
  310. const auto width = m_imageDescriptor.m_size.m_width;
  311. const auto height = m_imageDescriptor.m_size.m_height;
  312. if (width > 0 && height > 0)
  313. {
  314. // When "rasterizing" from uvs, a range of 0-1 has slightly different meanings depending on the sampler state.
  315. // For repeating states (Unbounded/None, Repeat), a uv value of 1 should wrap around back to our 0th pixel.
  316. // For clamping states (Clamp to Zero, Clamp to Edge), a uv value of 1 should point to the last pixel.
  317. // We assume here that the code handling sampler states has handled this for us in the clamping cases
  318. // by reducing our uv by a small delta value such that anything that wants the last pixel has a value
  319. // just slightly less than 1.
  320. // Keeping that in mind, we scale our uv from 0-1 to 0-image size inclusive. So a 4-pixel image will scale
  321. // uv values of 0-1 to 0-4, not 0-3 as you might expect. This is because we want the following range mappings:
  322. // [0 - 1/4) = pixel 0
  323. // [1/4 - 1/2) = pixel 1
  324. // [1/2 - 3/4) = pixel 2
  325. // [3/4 - 1) = pixel 3
  326. // [1 - 1 1/4) = pixel 0
  327. // ...
  328. // Also, based on our tiling settings, we extend the size of our image virtually by a factor of tilingX and tilingY.
  329. // A 16x16 pixel image and tilingX = tilingY = 1 maps the uv range of 0-1 to 0-16 pixels.
  330. // A 16x16 pixel image and tilingX = tilingY = 1.5 maps the uv range of 0-1 to 0-24 pixels.
  331. const AZ::Vector2 tiledDimensions(width * GetTilingX(), height * GetTilingY());
  332. // Convert from uv space back to pixel space
  333. AZ::Vector2 pixelLookup = (AZ::Vector2(uvw) * tiledDimensions);
  334. // UVs outside the 0-1 range are treated as infinitely tiling, so that we behave the same as the
  335. // other gradient generators. As mentioned above, if clamping is desired, we expect it to be applied
  336. // outside of this function.
  337. float pixelX = pixelLookup.GetX();
  338. float pixelY = pixelLookup.GetY();
  339. auto x = aznumeric_cast<AZ::u32>(pixelX) % width;
  340. auto y = aznumeric_cast<AZ::u32>(pixelY) % height;
  341. // Retrieve our pixel value based on our sampling type
  342. const float value = GetValueForSamplingType(samplingType, x, y, pixelX, pixelY);
  343. // Scale (inverse lerp) the value into a 0 - 1 range. We also clamp it because manual scale values could cause
  344. // the result to fall outside of the expected output range.
  345. return AZStd::clamp((value - m_offset) * m_multiplier, 0.0f, 1.0f);
  346. }
  347. }
  348. return defaultValue;
  349. }
  350. float ImageGradientComponent::InvertYAndGetPixelValue(AZ::u32 x, AZ::u32 invertedY) const
  351. {
  352. // This is a convenience method that flips the y before calling GetPixelValue() because
  353. // image heights are stored in the reverse direction of our world axes.
  354. const auto height = m_imageDescriptor.m_size.m_height;
  355. AZ::u32 y = (height - 1) - invertedY;
  356. return GetPixelValue(x, y);
  357. }
  358. float ImageGradientComponent::GetPixelValue(AZ::u32 x, AZ::u32 y) const
  359. {
  360. // For terrarium, there is a separate algorithm for retrieving the value
  361. float value = (m_currentChannel == ChannelToUse::Terrarium)
  362. ? GetTerrariumPixelValue(x, y)
  363. : AZ::RPI::GetImageDataPixelValue<float>(
  364. m_imageData, m_imageDescriptor, x, y, aznumeric_cast<AZ::u8>(m_currentChannel));
  365. return value;
  366. }
  367. float ImageGradientComponent::GetTerrariumPixelValue(AZ::u32 x, AZ::u32 y) const
  368. {
  369. float r = AZ::RPI::GetImageDataPixelValue<float>(m_imageData, m_imageDescriptor, x, y, aznumeric_cast<AZ::u8>(ChannelToUse::Red));
  370. float g = AZ::RPI::GetImageDataPixelValue<float>(m_imageData, m_imageDescriptor, x, y, aznumeric_cast<AZ::u8>(ChannelToUse::Green));
  371. float b = AZ::RPI::GetImageDataPixelValue<float>(m_imageData, m_imageDescriptor, x, y, aznumeric_cast<AZ::u8>(ChannelToUse::Blue));
  372. /*
  373. "Terrarium" is an image-based terrain file format as defined here: https://www.mapzen.com/blog/terrain-tile-service/
  374. According to the website: "Terrarium format PNG tiles contain raw elevation data in meters, in Mercator projection (EPSG:3857).
  375. All values are positive with a 32,768 offset, split into the red, green, and blue channels, with 16 bits of integer and 8 bits of fraction. To decode: (red * 256 + green + blue / 256) - 32768"
  376. This gives a range -32768 to 32768 meters at a constant 1/256 meter resolution. For reference, the lowest point on Earth (Mariana Trench) is at -10911 m, and the highest point (Mt Everest) is at 8848 m.
  377. The equation of (red * 256 + green + blue / 256) - 32768 is based on red/green/blue being u8 values, but we are getting float values back
  378. in the range of 0.0f - 1.0f, so the multipliers below have been modified slightly to account for that scaling
  379. */
  380. constexpr float redMultiplier = (255.0f * 256.0f) / 65536.0f;
  381. constexpr float greenMultiplier = 255.0f / 65536.0f;
  382. constexpr float blueMultiplier = (255.0f / 256.0f) / 65536.0f;
  383. return (r * redMultiplier) + (g * greenMultiplier) + (b * blueMultiplier);
  384. }
  385. void ImageGradientComponent::SetupMultiplierAndOffset(float min, float max)
  386. {
  387. // Pre-calculate values for scaling our input range to our output range of 0 - 1. Scaling just uses the standard inverse lerp
  388. // formula of "output = (input - min) / (max - min)", or "output = (input - offset) * multiplier" where
  389. // multiplier is 1 / (max - min) and offset is min. Precalculating this way lets us gracefully handle the case where min and
  390. // max are equal, since we don't want to divide by infinity, without needing to check for that case on every pixel.
  391. // If our range is equivalent, set our multiplier and offset so that
  392. // any input value > min goes to 1 and any input value <= min goes to 0.
  393. m_multiplier = (min == max) ? AZStd::numeric_limits<float>::max() : (1.0f / (max - min));
  394. m_offset = min;
  395. }
  396. void ImageGradientComponent::SetupDefaultMultiplierAndOffset()
  397. {
  398. // By default, don't perform any scaling - assume the input range is from 0 - 1, same as the desired output.
  399. m_minValue = 0.0f;
  400. m_maxValue = 1.0f;
  401. SetupMultiplierAndOffset(m_minValue, m_maxValue);
  402. }
  403. void ImageGradientComponent::SetupAutoScaleMultiplierAndOffset()
  404. {
  405. auto width = m_imageDescriptor.m_size.m_width;
  406. auto height = m_imageDescriptor.m_size.m_height;
  407. float minValue = AZStd::numeric_limits<float>::max();
  408. float maxValue = AZStd::numeric_limits<float>::lowest();
  409. // By looping through and calling GetPixelValue(), this will correctly get the min/max values from
  410. // either our image data or our modification buffer.
  411. for (uint32_t y = 0; y < height; y++)
  412. {
  413. for (uint32_t x = 0; x < width; x++)
  414. {
  415. float value = GetPixelValue(x, y);
  416. minValue = AZStd::min(value, minValue);
  417. maxValue = AZStd::max(value, maxValue);
  418. }
  419. }
  420. // Set our multiplier and offset based on the min / max values we found.
  421. m_minValue = minValue;
  422. m_maxValue = maxValue;
  423. SetupMultiplierAndOffset(m_minValue, m_maxValue);
  424. }
  425. void ImageGradientComponent::SetupManualScaleMultiplierAndOffset()
  426. {
  427. m_configuration.m_scaleRangeMin = AZStd::clamp(m_configuration.m_scaleRangeMin, 0.0f, 1.0f);
  428. m_configuration.m_scaleRangeMax = AZStd::clamp(m_configuration.m_scaleRangeMax, 0.0f, 1.0f);
  429. // Set our multiplier and offset based on the manual scale range. Note that the manual scale range might be less than the
  430. // input range and possibly even inverted.
  431. m_minValue = m_configuration.m_scaleRangeMin;
  432. m_maxValue = m_configuration.m_scaleRangeMax;
  433. SetupMultiplierAndOffset(m_minValue, m_maxValue);
  434. }
  435. float ImageGradientComponent::GetClampedValue(int32_t x, int32_t y) const
  436. {
  437. const auto width = m_imageDescriptor.m_size.m_width;
  438. const auto height = m_imageDescriptor.m_size.m_height;
  439. switch (m_gradientTransform.GetWrappingType())
  440. {
  441. case WrappingType::ClampToZero:
  442. if (x < 0 || x > m_maxX || y < 0 || y > m_maxY)
  443. {
  444. return 0.0f;
  445. }
  446. break;
  447. case WrappingType::ClampToEdge:
  448. x = AZ::GetClamp(x, 0, m_maxX);
  449. y = AZ::GetClamp(y, 0, m_maxY);
  450. break;
  451. case WrappingType::Mirror:
  452. if (x < 0)
  453. {
  454. x = -x;
  455. }
  456. if (y < 0)
  457. {
  458. y = -y;
  459. }
  460. if (x > m_maxX)
  461. {
  462. x = m_maxX - (x % width);
  463. }
  464. if (y > m_maxY)
  465. {
  466. y = m_maxY - (y % height);
  467. }
  468. break;
  469. case WrappingType::None:
  470. case WrappingType::Repeat:
  471. default:
  472. x = x % width;
  473. y = y % height;
  474. break;
  475. }
  476. return InvertYAndGetPixelValue(x, y);
  477. }
  478. void ImageGradientComponent::Get4x4Neighborhood(uint32_t x, uint32_t y, AZStd::array<AZStd::array<float, 4>, 4>& values) const
  479. {
  480. for (int32_t yIndex = 0; yIndex < 4; ++yIndex)
  481. {
  482. for (int32_t xIndex = 0; xIndex < 4; ++xIndex)
  483. {
  484. values[xIndex][yIndex] = GetClampedValue(x + xIndex - 1, y + yIndex - 1);
  485. }
  486. }
  487. }
  488. float ImageGradientComponent::GetValueForSamplingType(SamplingType samplingType, AZ::u32 x0, AZ::u32 y0, float pixelX, float pixelY) const
  489. {
  490. switch (samplingType)
  491. {
  492. case SamplingType::Point:
  493. default:
  494. // Retrieve the pixel value for the single point
  495. return InvertYAndGetPixelValue(x0, y0);
  496. case SamplingType::Bilinear:
  497. {
  498. // Bilinear interpolation
  499. //
  500. // |
  501. // |
  502. // | (x0,y1) * * (x1,y1)
  503. // |
  504. // | o (x,y)
  505. // |
  506. // | (x0,y0) * * (x1,y0)
  507. // |___________________________________
  508. //
  509. // The bilinear filtering samples from a grid around a desired pixel (x,y)
  510. // x0,y0 contains one corner of our grid square, x1,y1 contains the opposite corner, and deltaX/Y is the fractional
  511. // amount the position exists between those corners.
  512. // Ex: (3.3, 4.4) would have a x0,y0 of (3, 4), a x1,y1 of (4, 5), and a deltaX/Y of (0.3, 0.4).
  513. const float valueX0Y0 = GetClampedValue(x0, y0);
  514. const float valueX1Y0 = GetClampedValue(x0 + 1, y0);
  515. const float valueX0Y1 = GetClampedValue(x0, y0 + 1);
  516. const float valueX1Y1 = GetClampedValue(x0 + 1, y0 + 1);
  517. float deltaX = pixelX - floor(pixelX);
  518. float deltaY = pixelY - floor(pixelY);
  519. const float valueXY0 = AZ::Lerp(valueX0Y0, valueX1Y0, deltaX);
  520. const float valueXY1 = AZ::Lerp(valueX0Y1, valueX1Y1, deltaX);
  521. return AZ::Lerp(valueXY0, valueXY1, deltaY);
  522. }
  523. case SamplingType::Bicubic:
  524. {
  525. // Catmull-Rom style bicubic filtering. This uses the neighborhood of 16 samples to calculate a smooth curve for values
  526. // in between discrete sample locations. See https://en.wikipedia.org/wiki/Bicubic_interpolation
  527. // Simplified interpolation function
  528. auto cubicInterpolate = [](float p0, float p1, float p2, float p3, float delta) -> float
  529. {
  530. return p1 + 0.5f * delta * (p2 - p0 + delta * (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3 + delta * (3.0f * (p1 - p2) + p3 - p0)));
  531. };
  532. AZStd::array<AZStd::array<float, 4>, 4> values;
  533. Get4x4Neighborhood(x0, y0, values);
  534. float deltaX = pixelX - floor(pixelX);
  535. float deltaY = pixelY - floor(pixelY);
  536. const float valueXY0 = cubicInterpolate(values[0][0], values[1][0], values[2][0], values[3][0], deltaX);
  537. const float valueXY1 = cubicInterpolate(values[0][1], values[1][1], values[2][1], values[3][1], deltaX);
  538. const float valueXY2 = cubicInterpolate(values[0][2], values[1][2], values[2][2], values[3][2], deltaX);
  539. const float valueXY3 = cubicInterpolate(values[0][3], values[1][3], values[2][3], values[3][3], deltaX);
  540. return cubicInterpolate(valueXY0, valueXY1, valueXY2, valueXY3, deltaY);
  541. }
  542. }
  543. }
  544. void ImageGradientComponent::Activate()
  545. {
  546. // This will immediately call OnGradientTransformChanged and initialize m_gradientTransform.
  547. GradientTransformNotificationBus::Handler::BusConnect(GetEntityId());
  548. ImageGradientRequestBus::Handler::BusConnect(GetEntityId());
  549. AzFramework::PaintBrushNotificationBus::Handler::BusConnect({ GetEntityId(), GetId() });
  550. ImageGradientModificationBus::Handler::BusConnect(GetEntityId());
  551. // Invoke the QueueLoad before connecting to the AssetBus, so that
  552. // if the asset is already ready, then OnAssetReady will be triggered immediately
  553. UpdateCachedImageBufferData({}, {});
  554. m_configuration.m_imageAsset.QueueLoad(AZ::Data::AssetLoadParameters(nullptr, AZ::Data::AssetDependencyLoadRules::LoadAll));
  555. AZ::Data::AssetBus::Handler::BusConnect(m_configuration.m_imageAsset.GetId());
  556. // Connect to GradientRequestBus last so that everything is initialized before listening for gradient queries.
  557. GradientRequestBus::Handler::BusConnect(GetEntityId());
  558. }
  559. void ImageGradientComponent::Deactivate()
  560. {
  561. // Disconnect from GradientRequestBus first to ensure no queries are in process when deactivating.
  562. GradientRequestBus::Handler::BusDisconnect();
  563. AZ::Data::AssetBus::Handler::BusDisconnect();
  564. ImageGradientModificationBus::Handler::BusDisconnect();
  565. AzFramework::PaintBrushNotificationBus::Handler::BusDisconnect();
  566. ImageGradientRequestBus::Handler::BusDisconnect();
  567. GradientTransformNotificationBus::Handler::BusDisconnect();
  568. // Make sure we don't keep any cached references to the image asset data or the image modification buffer.
  569. UpdateCachedImageBufferData({}, {});
  570. m_configuration.m_imageAsset.Release();
  571. }
  572. bool ImageGradientComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
  573. {
  574. if (auto config = azrtti_cast<const ImageGradientConfig*>(baseConfig))
  575. {
  576. m_configuration = *config;
  577. return true;
  578. }
  579. return false;
  580. }
  581. bool ImageGradientComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
  582. {
  583. if (auto config = azrtti_cast<ImageGradientConfig*>(outBaseConfig))
  584. {
  585. *config = m_configuration;
  586. return true;
  587. }
  588. return false;
  589. }
  590. void ImageGradientComponent::UpdateCachedImageBufferData(
  591. const AZ::RHI::ImageDescriptor& imageDescriptor, AZStd::span<const uint8_t> imageData)
  592. {
  593. bool shouldRefreshModificationBuffer = false;
  594. // If we're changing our image data from our modification buffer to something else while it's active,
  595. // let's refresh the modification buffer with the new data.
  596. if (ModificationBufferIsActive() && (imageData.data() != m_imageData.data()))
  597. {
  598. shouldRefreshModificationBuffer = true;
  599. }
  600. m_imageDescriptor = imageDescriptor;
  601. m_imageData = imageData;
  602. m_maxX = imageDescriptor.m_size.m_width - 1;
  603. m_maxY = imageDescriptor.m_size.m_height - 1;
  604. if (shouldRefreshModificationBuffer)
  605. {
  606. m_modifiedImageData.resize(0);
  607. if (!m_imageData.empty())
  608. {
  609. CreateImageModificationBuffer();
  610. }
  611. }
  612. }
  613. void ImageGradientComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
  614. {
  615. {
  616. AZStd::unique_lock lock(m_queryMutex);
  617. m_configuration.m_imageAsset = asset;
  618. GetSubImageData();
  619. }
  620. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  621. }
  622. void ImageGradientComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
  623. {
  624. OnAssetReady(asset);
  625. }
  626. void ImageGradientComponent::OnGradientTransformChanged(const GradientTransform& newTransform)
  627. {
  628. AZStd::unique_lock lock(m_queryMutex);
  629. m_gradientTransform = newTransform;
  630. }
  631. void ImageGradientComponent::OnPaintModeBegin()
  632. {
  633. StartImageModification();
  634. }
  635. void ImageGradientComponent::OnPaintModeEnd()
  636. {
  637. EndImageModification();
  638. }
  639. AZ::Color ImageGradientComponent::OnGetColor(const AZ::Vector3& brushCenter) const
  640. {
  641. // Get the gradient value at the given point.
  642. // We use "GetPixelValuesByPosition" instead of "GetGradientValue" because we want to select unscaled, unsmoothed values.
  643. float gradientValue = 0.0f;
  644. GetPixelValuesByPosition(AZStd::span<const AZ::Vector3>(&brushCenter, 1), AZStd::span<float>(&gradientValue, 1));
  645. return AZ::Color(gradientValue, gradientValue, gradientValue, 1.0f);
  646. }
  647. void ImageGradientComponent::StartImageModification()
  648. {
  649. if (!m_imageModifier)
  650. {
  651. AZ_Assert(m_configuration.m_numImageModificationsActive == 0,
  652. "The imageModifier should exist since image modifications are already currently active.");
  653. m_imageModifier = AZStd::make_unique<ImageGradientModifier>(AZ::EntityComponentIdPair(GetEntityId(), GetId()));
  654. }
  655. if (m_modifiedImageData.empty())
  656. {
  657. CreateImageModificationBuffer();
  658. }
  659. m_configuration.m_numImageModificationsActive++;
  660. }
  661. void ImageGradientComponent::EndImageModification()
  662. {
  663. AZ_Assert(m_configuration.m_numImageModificationsActive > 0, "Mismatched calls to StartImageModification / EndImageModification");
  664. m_configuration.m_numImageModificationsActive--;
  665. if (m_configuration.m_numImageModificationsActive == 0)
  666. {
  667. m_imageModifier = {};
  668. }
  669. }
  670. AZStd::vector<float>* ImageGradientComponent::GetImageModificationBuffer()
  671. {
  672. // This will get replaced with safe/robust methods of modifying the image as paintbrush functionality
  673. // continues to get added to the Image Gradient component.
  674. return &m_modifiedImageData;
  675. }
  676. bool ImageGradientComponent::ImageIsModified() const
  677. {
  678. return !m_modifiedImageData.empty() && m_imageIsModified;
  679. }
  680. void ImageGradientComponent::CreateImageModificationBuffer()
  681. {
  682. if (m_imageData.empty())
  683. {
  684. AZ_Error("ImageGradientComponent", false,
  685. "Image data is empty. Make sure the image asset is fully loaded before attempting to modify it.");
  686. return;
  687. }
  688. const auto width = m_imageDescriptor.m_size.m_width;
  689. const auto height = m_imageDescriptor.m_size.m_height;
  690. // Track that the image hasn't been modified yet, even though we've created a modification buffer.
  691. m_imageIsModified = false;
  692. if (m_modifiedImageData.empty())
  693. {
  694. // Create a memory buffer for holding all of our modified image information.
  695. // We'll always use a buffer of floats to ensure that we're modifying at the highest precision possible.
  696. m_modifiedImageData.reserve(width * height);
  697. // Fill the buffer with all of our existing pixel values.
  698. for (uint32_t y = 0; y < height; y++)
  699. {
  700. for (uint32_t x = 0; x < width; x++)
  701. {
  702. float pixel = AZ::RPI::GetImageDataPixelValue<float>(
  703. m_imageData, m_imageDescriptor, x, y, aznumeric_cast<AZ::u8>(m_currentChannel));
  704. m_modifiedImageData.emplace_back(pixel);
  705. }
  706. }
  707. // Create an image descriptor describing our new buffer (correct width, height, and single-channel 32-bit float format)
  708. auto imageDescriptor =
  709. AZ::RHI::ImageDescriptor::Create2D(AZ::RHI::ImageBindFlags::None, width, height, AZ::RHI::Format::R32_FLOAT);
  710. // Set our imageData pointer to point to our modified data buffer.
  711. auto imageData = AZStd::span<const uint8_t>(
  712. reinterpret_cast<uint8_t*>(m_modifiedImageData.data()), m_modifiedImageData.size() * sizeof(float));
  713. UpdateCachedImageBufferData(imageDescriptor, imageData);
  714. }
  715. else
  716. {
  717. // If this triggers, we've somehow gotten our image modification buffer out of sync with the image descriptor information.
  718. AZ_Assert(m_modifiedImageData.size() == (width * height), "Image modification buffer exists but is the wrong size.");
  719. }
  720. }
  721. void ImageGradientComponent::ClearImageModificationBuffer()
  722. {
  723. AZ_Assert(m_configuration.m_numImageModificationsActive == 0, "Clearing modified image data while in modification mode!")
  724. m_modifiedImageData.resize(0);
  725. m_imageIsModified = false;
  726. }
  727. bool ImageGradientComponent::ModificationBufferIsActive() const
  728. {
  729. // The modification buffer is considered active if the modification buffer has data in it and
  730. // our cached imageData pointer is pointing into the modification buffer instead of into an image asset.
  731. return (m_modifiedImageData.data() != nullptr) && (!m_modifiedImageData.empty()) &&
  732. (reinterpret_cast<const void*>(m_imageData.data()) == reinterpret_cast<const void*>(m_modifiedImageData.data()));
  733. }
  734. float ImageGradientComponent::GetValue(const GradientSampleParams& sampleParams) const
  735. {
  736. AZ::Vector3 position(sampleParams.m_position);
  737. float value = 0.0f;
  738. GetValuesInternal(m_currentSamplingType, AZStd::span<AZ::Vector3>(&position, 1), AZStd::span<float>(&value, 1));
  739. return value;
  740. }
  741. void ImageGradientComponent::GetValues(AZStd::span<const AZ::Vector3> positions, AZStd::span<float> outValues) const
  742. {
  743. GetValuesInternal(m_currentSamplingType, positions, outValues);
  744. }
  745. void ImageGradientComponent::GetValuesInternal(
  746. SamplingType samplingType, AZStd::span<const AZ::Vector3> positions, AZStd::span<float> outValues) const
  747. {
  748. if (positions.size() != outValues.size())
  749. {
  750. AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size());
  751. return;
  752. }
  753. AZStd::shared_lock lock(m_queryMutex);
  754. // Just clear the output values and return if our cached image data hasn't been retrieved yet
  755. if (m_imageData.empty())
  756. {
  757. AZStd::fill(outValues.begin(), outValues.end(), 0.0f);
  758. return;
  759. }
  760. AZ::Vector3 uvw;
  761. bool wasPointRejected = false;
  762. for (size_t index = 0; index < positions.size(); index++)
  763. {
  764. m_gradientTransform.TransformPositionToUVWNormalized(positions[index], uvw, wasPointRejected);
  765. if (!wasPointRejected)
  766. {
  767. outValues[index] = GetValueFromImageData(samplingType, uvw, 0.0f);
  768. }
  769. else
  770. {
  771. outValues[index] = 0.0f;
  772. }
  773. }
  774. }
  775. AZStd::string ImageGradientComponent::GetImageAssetPath() const
  776. {
  777. AZStd::string assetPathString;
  778. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetPathString, &AZ::Data::AssetCatalogRequests::GetAssetPathById, m_configuration.m_imageAsset.GetId());
  779. return assetPathString;
  780. }
  781. AZStd::string ImageGradientComponent::GetImageAssetSourcePath() const
  782. {
  783. // The m_imageAsset path is to the product, so it will have an additional extension:
  784. // e.g. image.png.streamingimage
  785. // So to provide just the source asset path we need to remove the product extension
  786. AZStd::string imageAssetPath = GetImageAssetPath();
  787. AZ::IO::Path imageSourceAssetPath = AZ::IO::Path(imageAssetPath).ReplaceExtension("");
  788. return imageSourceAssetPath.c_str();
  789. }
  790. void ImageGradientComponent::SetImageAssetPath(const AZStd::string& assetPath)
  791. {
  792. AZ::Data::AssetId assetId;
  793. if (!assetPath.empty())
  794. {
  795. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  796. assetId, &AZ::Data::AssetCatalogRequests::GetAssetIdByPath, assetPath.c_str(), AZ::Data::s_invalidAssetType, false);
  797. if (!assetId.IsValid())
  798. {
  799. // This case can occur either if the asset path is completely wrong, or if it's correct but the asset is still in
  800. // the process of being created and being processed. Even though the second possibility is valid,
  801. AZ_Warning(
  802. "GradientSignal", false, "Can't find an Asset ID for %s, SetImageAssetPath() will be ignored.", assetPath.c_str());
  803. return;
  804. }
  805. }
  806. // If we were given a valid asset, then make sure it is the right type
  807. if (assetId.IsValid())
  808. {
  809. AZ::Data::AssetInfo assetInfo;
  810. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, assetId);
  811. if (assetInfo.m_assetType != azrtti_typeid<AZ::RPI::StreamingImageAsset>())
  812. {
  813. AZ_Warning("GradientSignal", false, "Asset type for %s is not AZ::RPI::StreamingImageAsset, will be ignored", assetPath.c_str());
  814. return;
  815. }
  816. }
  817. AZ::Data::Asset<AZ::RPI::StreamingImageAsset> imageAsset;
  818. if (assetId.IsValid())
  819. {
  820. imageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(
  821. assetId, azrtti_typeid<AZ::RPI::StreamingImageAsset>(), m_configuration.m_imageAsset.GetAutoLoadBehavior());
  822. }
  823. SetImageAsset(imageAsset);
  824. }
  825. void ImageGradientComponent::SetImageAssetSourcePath(const AZStd::string& assetPath)
  826. {
  827. // SetImageAssetPath expects a product asset path, so we need to append the product
  828. // extension to the source asset path we are given
  829. AZStd::string productAssetPath(assetPath);
  830. productAssetPath += ".streamingimage";
  831. SetImageAssetPath(productAssetPath);
  832. }
  833. AZ::Data::Asset<AZ::RPI::StreamingImageAsset> ImageGradientComponent::GetImageAsset() const
  834. {
  835. return m_configuration.m_imageAsset;
  836. }
  837. void ImageGradientComponent::SetImageAsset(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& asset)
  838. {
  839. // If we're setting the component to the same asset we're already using, then early-out.
  840. if (asset.GetId() == m_configuration.m_imageAsset.GetId())
  841. {
  842. return;
  843. }
  844. // Stop listening for the current image asset.
  845. AZ::Data::AssetBus::Handler::BusDisconnect(m_configuration.m_imageAsset.GetId());
  846. {
  847. // Only hold the lock during the actual data changes, to ensure that we aren't mid-query when changing it, but also to
  848. // minimize the total lock duration. Also, because we've disconnected from the imageAsset Asset bus prior to locking this,
  849. // we won't get any OnAsset* notifications while we're changing out the asset.
  850. AZStd::unique_lock lock(m_queryMutex);
  851. // Clear our cached image data unless we're currently using a modification buffer.
  852. // If we're using a modification buffer, we want to keep it active until the new image has finished loading in.
  853. if (!asset.IsReady() && !ModificationBufferIsActive())
  854. {
  855. UpdateCachedImageBufferData({}, {});
  856. }
  857. m_configuration.m_imageAsset = asset;
  858. }
  859. if (m_configuration.m_imageAsset.GetId().IsValid())
  860. {
  861. // 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
  862. // that doesn't exist yet if it was just created from the Editor component.
  863. AZ::Data::AssetInfo assetInfo;
  864. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  865. assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, m_configuration.m_imageAsset.GetId());
  866. // Only queue the load if it appears in the Asset Catalog. If it doesn't, we'll get notified when it shows up.
  867. if (assetInfo.m_assetId.IsValid())
  868. {
  869. m_configuration.m_imageAsset.QueueLoad(AZ::Data::AssetLoadParameters(nullptr, AZ::Data::AssetDependencyLoadRules::LoadAll));
  870. }
  871. // Start listening for all events for this asset.
  872. AZ::Data::AssetBus::Handler::BusConnect(m_configuration.m_imageAsset.GetId());
  873. }
  874. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  875. }
  876. uint32_t ImageGradientComponent::GetImageHeight() const
  877. {
  878. return m_imageDescriptor.m_size.m_height;
  879. }
  880. uint32_t ImageGradientComponent::GetImageWidth() const
  881. {
  882. return m_imageDescriptor.m_size.m_width;
  883. }
  884. AZ::Vector2 ImageGradientComponent::GetImagePixelsPerMeter() const
  885. {
  886. // Get the number of pixels in our image that maps to each meter based on the tiling and gradient transform settings.
  887. const auto width = m_imageDescriptor.m_size.m_width;
  888. const auto height = m_imageDescriptor.m_size.m_height;
  889. if (width > 0 && height > 0)
  890. {
  891. // The number of pixels per meter depends on a combination of the tiling, the scale, and the frequency zoom.
  892. // All of these numbers together determine how often the image repeats within the shape bounds.
  893. const AZ::Vector3 transformScale = m_gradientTransform.GetScale();
  894. const float transformZoom = m_gradientTransform.GetFrequencyZoom();
  895. // Get the local bounds for the gradient. This determines the total number of meters in each direction that the image
  896. // repeats are mapped onto.
  897. const AZ::Aabb localBounds = m_gradientTransform.GetBounds();
  898. const AZ::Vector2 boundsMeters(localBounds.GetExtents());
  899. const AZ::Vector2 imagePixelsInBounds(width * GetTilingX(), height * GetTilingY());
  900. return (imagePixelsInBounds * transformZoom) / (boundsMeters * AZ::Vector2(transformScale));
  901. }
  902. return AZ::Vector2::CreateZero();
  903. }
  904. float ImageGradientComponent::GetTilingX() const
  905. {
  906. return m_configuration.m_tiling.GetX();
  907. }
  908. void ImageGradientComponent::SetTilingX(float tilingX)
  909. {
  910. // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
  911. // execute an arbitrary amount of logic, including calls back to this component.
  912. {
  913. AZStd::unique_lock lock(m_queryMutex);
  914. m_configuration.m_tiling.SetX(tilingX);
  915. }
  916. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  917. }
  918. float ImageGradientComponent::GetTilingY() const
  919. {
  920. return m_configuration.m_tiling.GetY();
  921. }
  922. void ImageGradientComponent::SetTilingY(float tilingY)
  923. {
  924. // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
  925. // execute an arbitrary amount of logic, including calls back to this component.
  926. {
  927. AZStd::unique_lock lock(m_queryMutex);
  928. m_configuration.m_tiling.SetY(tilingY);
  929. }
  930. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  931. }
  932. PixelIndex ImageGradientComponent::GetPixelIndexForPositionInternal(const AZ::Vector3& position) const
  933. {
  934. const auto width = m_imageDescriptor.m_size.m_width;
  935. const auto height = m_imageDescriptor.m_size.m_height;
  936. const AZ::Vector3 tiledDimensions((width * GetTilingX()), (height * GetTilingY()), 0.0f);
  937. // Use the Gradient Transform to convert from world space to image space.
  938. AZ::Vector3 uvw = position;
  939. bool wasPointRejected = true;
  940. m_gradientTransform.TransformPositionToUVWNormalized(position, uvw, wasPointRejected);
  941. if ((width > 0) && (height > 0) && (!wasPointRejected))
  942. {
  943. // Since the Image Gradient also has a tiling factor, scale the returned image space value
  944. // by the tiling factor to get to the specific pixel requested.
  945. AZ::Vector3 pixelLookup = (uvw * tiledDimensions);
  946. // UVs outside the 0-1 range are treated as infinitely tiling, we mod the values to bring them back into image bounds.
  947. float pixelX = pixelLookup.GetX();
  948. float pixelY = pixelLookup.GetY();
  949. auto x = aznumeric_cast<AZ::u32>(pixelX) % width;
  950. auto y = aznumeric_cast<AZ::u32>(pixelY) % height;
  951. // Flip the y because images are stored in reverse of our world axes
  952. y = (height - 1) - y;
  953. return PixelIndex(aznumeric_cast<int16_t>(x), aznumeric_cast<int16_t>(y));
  954. }
  955. else
  956. {
  957. return PixelIndex(aznumeric_cast<int16_t>(-1), aznumeric_cast<int16_t>(-1));
  958. }
  959. }
  960. bool ImageGradientComponent::PixelIndexIsValid(const PixelIndex& pixelIndex) const
  961. {
  962. const auto width = m_imageDescriptor.m_size.m_width;
  963. const auto height = m_imageDescriptor.m_size.m_height;
  964. const auto& [x, y] = pixelIndex;
  965. return ((x >= 0) && (x < aznumeric_cast<int16_t>(width)) && (y >= 0) && (y < aznumeric_cast<int16_t>(height)));
  966. }
  967. void ImageGradientComponent::GetPixelValuesByPosition(AZStd::span<const AZ::Vector3> positions, AZStd::span<float> outValues) const
  968. {
  969. AZStd::shared_lock lock(m_queryMutex);
  970. for (size_t index = 0; index < positions.size(); index++)
  971. {
  972. // We use the Pixel* APIs because we want to end up with raw unscaled, unsmoothed pixel values instead of final scaled
  973. // and smoothed gradient values.
  974. auto pixelIndex = GetPixelIndexForPositionInternal(positions[index]);
  975. if (PixelIndexIsValid(pixelIndex))
  976. {
  977. const auto& [x, y] = pixelIndex;
  978. outValues[index] = GetPixelValue(x, y);
  979. // This intentionally does not scale the pixel values with the multiplier and offset. We're trying to
  980. // get pixel values, not final gradient values.
  981. }
  982. }
  983. }
  984. void ImageGradientComponent::GetPixelIndicesForPositions(
  985. AZStd::span<const AZ::Vector3> positions, AZStd::span<PixelIndex> outIndices) const
  986. {
  987. AZStd::shared_lock lock(m_queryMutex);
  988. for (size_t index = 0; index < positions.size(); index++)
  989. {
  990. outIndices[index] = GetPixelIndexForPositionInternal(positions[index]);
  991. }
  992. }
  993. void ImageGradientComponent::GetPixelValuesByPixelIndex(AZStd::span<const PixelIndex> positions, AZStd::span<float> outValues) const
  994. {
  995. AZStd::shared_lock lock(m_queryMutex);
  996. for (size_t index = 0; index < positions.size(); index++)
  997. {
  998. if (PixelIndexIsValid(positions[index]))
  999. {
  1000. const auto& [x, y] = positions[index];
  1001. // For terrarium, there is a separate algorithm for retrieving the value.
  1002. outValues[index] = GetPixelValue(x, y);
  1003. // This intentionally does not scale the pixel values with the multiplier and offset. We're trying to
  1004. // get pixel values, not final gradient values.
  1005. }
  1006. }
  1007. }
  1008. void ImageGradientComponent::SetPixelValuesByPosition(AZStd::span<const AZ::Vector3> positions, AZStd::span<const float> values)
  1009. {
  1010. AZStd::vector<PixelIndex> pixelIndices(positions.size());
  1011. GetPixelIndicesForPositions(positions, pixelIndices);
  1012. SetPixelValuesByPixelIndex(pixelIndices, values);
  1013. }
  1014. void ImageGradientComponent::SetPixelValuesByPixelIndex(AZStd::span<const PixelIndex> positions, AZStd::span<const float> values)
  1015. {
  1016. bool refreshEntireImage = false;
  1017. // We perform all the modfications with the scoped queryMutex lock. This may cause us to send an OnCompositionChanged() message,
  1018. // which we'll need to do outside of the lock to avoid any potential deadlocks.
  1019. {
  1020. AZStd::unique_lock lock(m_queryMutex);
  1021. if (m_modifiedImageData.empty())
  1022. {
  1023. AZ_Error(
  1024. "ImageGradientComponent", false, "Image modification mode needs to be started before the image values can be set.");
  1025. return;
  1026. }
  1027. const auto width = m_imageDescriptor.m_size.m_width;
  1028. const auto height = m_imageDescriptor.m_size.m_height;
  1029. // No pixels, so nothing to modify.
  1030. if ((width == 0) || (height == 0))
  1031. {
  1032. return;
  1033. }
  1034. // If we're set to auto-scaling, we need to do a bit more tracking while modifying our data to see if our auto-scaling
  1035. // values have changed. If so, we'll need to refresh the entire image.
  1036. if (m_currentScaleType == CustomScaleType::Auto)
  1037. {
  1038. const float preModificationMinValue = m_minValue;
  1039. const float preModificationMaxValue = m_maxValue;
  1040. // This tracks whether or not we need to loop through *all* the pixels to recalculate the min/max values.
  1041. bool recalculateMinMax = false;
  1042. for (size_t index = 0; index < positions.size(); index++)
  1043. {
  1044. if (PixelIndexIsValid(positions[index]))
  1045. {
  1046. const auto& [x, y] = positions[index];
  1047. auto& pixelToModify = m_modifiedImageData[(y * width) + x];
  1048. // If the value we're modifying was previously either our old min or old max value,
  1049. // and we're setting it to a value that's inside the min/max range, then we're going to need
  1050. // to fully recalculate our min/max values and our scaling multiplier and offset, since
  1051. // the range might have shrunk. If they've changed, then we'll need to refresh the entire image.
  1052. if ((pixelToModify == preModificationMinValue) && (values[index] > preModificationMinValue))
  1053. {
  1054. recalculateMinMax = true;
  1055. }
  1056. else if ((pixelToModify == preModificationMaxValue) && (values[index] < preModificationMaxValue))
  1057. {
  1058. recalculateMinMax = true;
  1059. }
  1060. // Modify the correct pixel in our modification buffer.
  1061. pixelToModify = values[index];
  1062. // Potentially expand the min/max range of our image.
  1063. // If these expand, we'll need to recalculate our auto-scale multiplier and offset and refresh the entire image.
  1064. m_minValue = AZStd::min(m_minValue, values[index]);
  1065. m_maxValue = AZStd::max(m_maxValue, values[index]);
  1066. // Track that we've modified the image
  1067. m_imageIsModified = true;
  1068. }
  1069. }
  1070. if (recalculateMinMax)
  1071. {
  1072. // We've potentially shrunk our min/max range, so recalculate the min/max values, multiplier, and offset.
  1073. // We'll do this before the next check, which checks to see if our min/max range has changed.
  1074. SetupAutoScaleMultiplierAndOffset();
  1075. }
  1076. // If our modifications have either expanded or contracted our min/max range, recalculate the scaling multiplier
  1077. // and offset and refresh the entire image.
  1078. if ((preModificationMinValue != m_minValue) || (preModificationMaxValue != m_maxValue))
  1079. {
  1080. SetupMultiplierAndOffset(m_minValue, m_maxValue);
  1081. refreshEntireImage = true;
  1082. }
  1083. }
  1084. else
  1085. {
  1086. // If we're set to manual scale or no scale, modifications are just a simple write loop.
  1087. for (size_t index = 0; index < positions.size(); index++)
  1088. {
  1089. if (PixelIndexIsValid(positions[index]))
  1090. {
  1091. const auto& [x, y] = positions[index];
  1092. // Modify the correct pixel in our modification buffer.
  1093. m_modifiedImageData[(y * width) + x] = values[index];
  1094. // Track that we've modified the image
  1095. m_imageIsModified = true;
  1096. }
  1097. }
  1098. }
  1099. }
  1100. // If we need to refresh the entire image, send the notification outside of the scope block containing the queryMutex lock
  1101. // to avoid any potential deadlocks.
  1102. if (refreshEntireImage)
  1103. {
  1104. LmbrCentral::DependencyNotificationBus::Event(
  1105. GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  1106. }
  1107. }
  1108. }