3
0

ImageGradientComponent.cpp 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322
  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(!ModificationBufferIsActive(), "Clearing modified image data while it's still in use as the active asset!");
  724. AZ_Assert(m_configuration.m_numImageModificationsActive == 0, "Clearing modified image data while in modification mode!")
  725. m_modifiedImageData.resize(0);
  726. m_imageIsModified = false;
  727. }
  728. bool ImageGradientComponent::ModificationBufferIsActive() const
  729. {
  730. // The modification buffer is considered active if the modification buffer has data in it and
  731. // our cached imageData pointer is pointing into the modification buffer instead of into an image asset.
  732. return (m_modifiedImageData.data() != nullptr) &&
  733. (reinterpret_cast<const void*>(m_imageData.data()) == reinterpret_cast<const void*>(m_modifiedImageData.data()));
  734. }
  735. float ImageGradientComponent::GetValue(const GradientSampleParams& sampleParams) const
  736. {
  737. AZ::Vector3 position(sampleParams.m_position);
  738. float value = 0.0f;
  739. GetValuesInternal(m_currentSamplingType, AZStd::span<AZ::Vector3>(&position, 1), AZStd::span<float>(&value, 1));
  740. return value;
  741. }
  742. void ImageGradientComponent::GetValues(AZStd::span<const AZ::Vector3> positions, AZStd::span<float> outValues) const
  743. {
  744. GetValuesInternal(m_currentSamplingType, positions, outValues);
  745. }
  746. void ImageGradientComponent::GetValuesInternal(
  747. SamplingType samplingType, AZStd::span<const AZ::Vector3> positions, AZStd::span<float> outValues) const
  748. {
  749. if (positions.size() != outValues.size())
  750. {
  751. AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size());
  752. return;
  753. }
  754. AZStd::shared_lock lock(m_queryMutex);
  755. // Just clear the output values and return if our cached image data hasn't been retrieved yet
  756. if (m_imageData.empty())
  757. {
  758. AZStd::fill(outValues.begin(), outValues.end(), 0.0f);
  759. return;
  760. }
  761. AZ::Vector3 uvw;
  762. bool wasPointRejected = false;
  763. for (size_t index = 0; index < positions.size(); index++)
  764. {
  765. m_gradientTransform.TransformPositionToUVWNormalized(positions[index], uvw, wasPointRejected);
  766. if (!wasPointRejected)
  767. {
  768. outValues[index] = GetValueFromImageData(samplingType, uvw, 0.0f);
  769. }
  770. else
  771. {
  772. outValues[index] = 0.0f;
  773. }
  774. }
  775. }
  776. AZStd::string ImageGradientComponent::GetImageAssetPath() const
  777. {
  778. AZStd::string assetPathString;
  779. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetPathString, &AZ::Data::AssetCatalogRequests::GetAssetPathById, m_configuration.m_imageAsset.GetId());
  780. return assetPathString;
  781. }
  782. AZStd::string ImageGradientComponent::GetImageAssetSourcePath() const
  783. {
  784. // The m_imageAsset path is to the product, so it will have an additional extension:
  785. // e.g. image.png.streamingimage
  786. // So to provide just the source asset path we need to remove the product extension
  787. AZStd::string imageAssetPath = GetImageAssetPath();
  788. AZ::IO::Path imageSourceAssetPath = AZ::IO::Path(imageAssetPath).ReplaceExtension("");
  789. return imageSourceAssetPath.c_str();
  790. }
  791. void ImageGradientComponent::SetImageAssetPath(const AZStd::string& assetPath)
  792. {
  793. AZ::Data::AssetId assetId;
  794. if (!assetPath.empty())
  795. {
  796. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  797. assetId, &AZ::Data::AssetCatalogRequests::GetAssetIdByPath, assetPath.c_str(), AZ::Data::s_invalidAssetType, false);
  798. if (!assetId.IsValid())
  799. {
  800. // This case can occur either if the asset path is completely wrong, or if it's correct but the asset is still in
  801. // the process of being created and being processed. Even though the second possibility is valid,
  802. AZ_Warning(
  803. "GradientSignal", false, "Can't find an Asset ID for %s, SetImageAssetPath() will be ignored.", assetPath.c_str());
  804. return;
  805. }
  806. }
  807. // If we were given a valid asset, then make sure it is the right type
  808. if (assetId.IsValid())
  809. {
  810. AZ::Data::AssetInfo assetInfo;
  811. AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, assetId);
  812. if (assetInfo.m_assetType != azrtti_typeid<AZ::RPI::StreamingImageAsset>())
  813. {
  814. AZ_Warning("GradientSignal", false, "Asset type for %s is not AZ::RPI::StreamingImageAsset, will be ignored", assetPath.c_str());
  815. return;
  816. }
  817. }
  818. AZ::Data::Asset<AZ::RPI::StreamingImageAsset> imageAsset;
  819. if (assetId.IsValid())
  820. {
  821. imageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(
  822. assetId, azrtti_typeid<AZ::RPI::StreamingImageAsset>(), m_configuration.m_imageAsset.GetAutoLoadBehavior());
  823. }
  824. SetImageAsset(imageAsset);
  825. }
  826. void ImageGradientComponent::SetImageAssetSourcePath(const AZStd::string& assetPath)
  827. {
  828. // SetImageAssetPath expects a product asset path, so we need to append the product
  829. // extension to the source asset path we are given
  830. AZStd::string productAssetPath(assetPath);
  831. productAssetPath += ".streamingimage";
  832. SetImageAssetPath(productAssetPath);
  833. }
  834. AZ::Data::Asset<AZ::RPI::StreamingImageAsset> ImageGradientComponent::GetImageAsset() const
  835. {
  836. return m_configuration.m_imageAsset;
  837. }
  838. void ImageGradientComponent::SetImageAsset(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& asset)
  839. {
  840. // If we're setting the component to the same asset we're already using, then early-out.
  841. if (asset.GetId() == m_configuration.m_imageAsset.GetId())
  842. {
  843. return;
  844. }
  845. // Stop listening for the current image asset.
  846. AZ::Data::AssetBus::Handler::BusDisconnect(m_configuration.m_imageAsset.GetId());
  847. {
  848. // Only hold the lock during the actual data changes, to ensure that we aren't mid-query when changing it, but also to
  849. // minimize the total lock duration. Also, because we've disconnected from the imageAsset Asset bus prior to locking this,
  850. // we won't get any OnAsset* notifications while we're changing out the asset.
  851. AZStd::unique_lock lock(m_queryMutex);
  852. // Clear our cached image data unless we're currently using a modification buffer.
  853. // If we're using a modification buffer, we want to keep it active until the new image has finished loading in.
  854. if (!asset.IsReady() && !ModificationBufferIsActive())
  855. {
  856. UpdateCachedImageBufferData({}, {});
  857. }
  858. m_configuration.m_imageAsset = asset;
  859. }
  860. if (m_configuration.m_imageAsset.GetId().IsValid())
  861. {
  862. // 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
  863. // that doesn't exist yet if it was just created from the Editor component.
  864. AZ::Data::AssetInfo assetInfo;
  865. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  866. assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, m_configuration.m_imageAsset.GetId());
  867. // Only queue the load if it appears in the Asset Catalog. If it doesn't, we'll get notified when it shows up.
  868. if (assetInfo.m_assetId.IsValid())
  869. {
  870. m_configuration.m_imageAsset.QueueLoad(AZ::Data::AssetLoadParameters(nullptr, AZ::Data::AssetDependencyLoadRules::LoadAll));
  871. }
  872. // Start listening for all events for this asset.
  873. AZ::Data::AssetBus::Handler::BusConnect(m_configuration.m_imageAsset.GetId());
  874. }
  875. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  876. }
  877. uint32_t ImageGradientComponent::GetImageHeight() const
  878. {
  879. return m_imageDescriptor.m_size.m_height;
  880. }
  881. uint32_t ImageGradientComponent::GetImageWidth() const
  882. {
  883. return m_imageDescriptor.m_size.m_width;
  884. }
  885. AZ::Vector2 ImageGradientComponent::GetImagePixelsPerMeter() const
  886. {
  887. // Get the number of pixels in our image that maps to each meter based on the tiling and gradient transform settings.
  888. const auto width = m_imageDescriptor.m_size.m_width;
  889. const auto height = m_imageDescriptor.m_size.m_height;
  890. if (width > 0 && height > 0)
  891. {
  892. // The number of pixels per meter depends on a combination of the tiling, the scale, and the frequency zoom.
  893. // All of these numbers together determine how often the image repeats within the shape bounds.
  894. const AZ::Vector3 transformScale = m_gradientTransform.GetScale();
  895. const float transformZoom = m_gradientTransform.GetFrequencyZoom();
  896. // Get the local bounds for the gradient. This determines the total number of meters in each direction that the image
  897. // repeats are mapped onto.
  898. const AZ::Aabb localBounds = m_gradientTransform.GetBounds();
  899. const AZ::Vector2 boundsMeters(localBounds.GetExtents());
  900. const AZ::Vector2 imagePixelsInBounds(width * GetTilingX(), height * GetTilingY());
  901. return (imagePixelsInBounds * transformZoom) / (boundsMeters * AZ::Vector2(transformScale));
  902. }
  903. return AZ::Vector2::CreateZero();
  904. }
  905. float ImageGradientComponent::GetTilingX() const
  906. {
  907. return m_configuration.m_tiling.GetX();
  908. }
  909. void ImageGradientComponent::SetTilingX(float tilingX)
  910. {
  911. // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
  912. // execute an arbitrary amount of logic, including calls back to this component.
  913. {
  914. AZStd::unique_lock lock(m_queryMutex);
  915. m_configuration.m_tiling.SetX(tilingX);
  916. }
  917. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  918. }
  919. float ImageGradientComponent::GetTilingY() const
  920. {
  921. return m_configuration.m_tiling.GetY();
  922. }
  923. void ImageGradientComponent::SetTilingY(float tilingY)
  924. {
  925. // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
  926. // execute an arbitrary amount of logic, including calls back to this component.
  927. {
  928. AZStd::unique_lock lock(m_queryMutex);
  929. m_configuration.m_tiling.SetY(tilingY);
  930. }
  931. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  932. }
  933. PixelIndex ImageGradientComponent::GetPixelIndexForPositionInternal(const AZ::Vector3& position) const
  934. {
  935. const auto width = m_imageDescriptor.m_size.m_width;
  936. const auto height = m_imageDescriptor.m_size.m_height;
  937. const AZ::Vector3 tiledDimensions((width * GetTilingX()), (height * GetTilingY()), 0.0f);
  938. // Use the Gradient Transform to convert from world space to image space.
  939. AZ::Vector3 uvw = position;
  940. bool wasPointRejected = true;
  941. m_gradientTransform.TransformPositionToUVWNormalized(position, uvw, wasPointRejected);
  942. if ((width > 0) && (height > 0) && (!wasPointRejected))
  943. {
  944. // Since the Image Gradient also has a tiling factor, scale the returned image space value
  945. // by the tiling factor to get to the specific pixel requested.
  946. AZ::Vector3 pixelLookup = (uvw * tiledDimensions);
  947. // UVs outside the 0-1 range are treated as infinitely tiling, we mod the values to bring them back into image bounds.
  948. float pixelX = pixelLookup.GetX();
  949. float pixelY = pixelLookup.GetY();
  950. auto x = aznumeric_cast<AZ::u32>(pixelX) % width;
  951. auto y = aznumeric_cast<AZ::u32>(pixelY) % height;
  952. // Flip the y because images are stored in reverse of our world axes
  953. y = (height - 1) - y;
  954. return PixelIndex(aznumeric_cast<int16_t>(x), aznumeric_cast<int16_t>(y));
  955. }
  956. else
  957. {
  958. return PixelIndex(aznumeric_cast<int16_t>(-1), aznumeric_cast<int16_t>(-1));
  959. }
  960. }
  961. bool ImageGradientComponent::PixelIndexIsValid(const PixelIndex& pixelIndex) const
  962. {
  963. const auto width = m_imageDescriptor.m_size.m_width;
  964. const auto height = m_imageDescriptor.m_size.m_height;
  965. const auto& [x, y] = pixelIndex;
  966. return ((x >= 0) && (x < aznumeric_cast<int16_t>(width)) && (y >= 0) && (y < aznumeric_cast<int16_t>(height)));
  967. }
  968. void ImageGradientComponent::GetPixelValuesByPosition(AZStd::span<const AZ::Vector3> positions, AZStd::span<float> outValues) const
  969. {
  970. AZStd::shared_lock lock(m_queryMutex);
  971. for (size_t index = 0; index < positions.size(); index++)
  972. {
  973. // We use the Pixel* APIs because we want to end up with raw unscaled, unsmoothed pixel values instead of final scaled
  974. // and smoothed gradient values.
  975. auto pixelIndex = GetPixelIndexForPositionInternal(positions[index]);
  976. if (PixelIndexIsValid(pixelIndex))
  977. {
  978. const auto& [x, y] = pixelIndex;
  979. outValues[index] = GetPixelValue(x, y);
  980. // This intentionally does not scale the pixel values with the multiplier and offset. We're trying to
  981. // get pixel values, not final gradient values.
  982. }
  983. }
  984. }
  985. void ImageGradientComponent::GetPixelIndicesForPositions(
  986. AZStd::span<const AZ::Vector3> positions, AZStd::span<PixelIndex> outIndices) const
  987. {
  988. AZStd::shared_lock lock(m_queryMutex);
  989. for (size_t index = 0; index < positions.size(); index++)
  990. {
  991. outIndices[index] = GetPixelIndexForPositionInternal(positions[index]);
  992. }
  993. }
  994. void ImageGradientComponent::GetPixelValuesByPixelIndex(AZStd::span<const PixelIndex> positions, AZStd::span<float> outValues) const
  995. {
  996. AZStd::shared_lock lock(m_queryMutex);
  997. for (size_t index = 0; index < positions.size(); index++)
  998. {
  999. if (PixelIndexIsValid(positions[index]))
  1000. {
  1001. const auto& [x, y] = positions[index];
  1002. // For terrarium, there is a separate algorithm for retrieving the value.
  1003. outValues[index] = GetPixelValue(x, y);
  1004. // This intentionally does not scale the pixel values with the multiplier and offset. We're trying to
  1005. // get pixel values, not final gradient values.
  1006. }
  1007. }
  1008. }
  1009. void ImageGradientComponent::SetPixelValuesByPosition(AZStd::span<const AZ::Vector3> positions, AZStd::span<const float> values)
  1010. {
  1011. AZStd::vector<PixelIndex> pixelIndices(positions.size());
  1012. GetPixelIndicesForPositions(positions, pixelIndices);
  1013. SetPixelValuesByPixelIndex(pixelIndices, values);
  1014. }
  1015. void ImageGradientComponent::SetPixelValuesByPixelIndex(AZStd::span<const PixelIndex> positions, AZStd::span<const float> values)
  1016. {
  1017. bool refreshEntireImage = false;
  1018. // We perform all the modfications with the scoped queryMutex lock. This may cause us to send an OnCompositionChanged() message,
  1019. // which we'll need to do outside of the lock to avoid any potential deadlocks.
  1020. {
  1021. AZStd::unique_lock lock(m_queryMutex);
  1022. if (m_modifiedImageData.empty())
  1023. {
  1024. AZ_Error(
  1025. "ImageGradientComponent", false, "Image modification mode needs to be started before the image values can be set.");
  1026. return;
  1027. }
  1028. const auto width = m_imageDescriptor.m_size.m_width;
  1029. const auto height = m_imageDescriptor.m_size.m_height;
  1030. // No pixels, so nothing to modify.
  1031. if ((width == 0) || (height == 0))
  1032. {
  1033. return;
  1034. }
  1035. // 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
  1036. // values have changed. If so, we'll need to refresh the entire image.
  1037. if (m_currentScaleType == CustomScaleType::Auto)
  1038. {
  1039. const float preModificationMinValue = m_minValue;
  1040. const float preModificationMaxValue = m_maxValue;
  1041. // This tracks whether or not we need to loop through *all* the pixels to recalculate the min/max values.
  1042. bool recalculateMinMax = false;
  1043. for (size_t index = 0; index < positions.size(); index++)
  1044. {
  1045. if (PixelIndexIsValid(positions[index]))
  1046. {
  1047. const auto& [x, y] = positions[index];
  1048. auto& pixelToModify = m_modifiedImageData[(y * width) + x];
  1049. // If the value we're modifying was previously either our old min or old max value,
  1050. // and we're setting it to a value that's inside the min/max range, then we're going to need
  1051. // to fully recalculate our min/max values and our scaling multiplier and offset, since
  1052. // the range might have shrunk. If they've changed, then we'll need to refresh the entire image.
  1053. if ((pixelToModify == preModificationMinValue) && (values[index] > preModificationMinValue))
  1054. {
  1055. recalculateMinMax = true;
  1056. }
  1057. else if ((pixelToModify == preModificationMaxValue) && (values[index] < preModificationMaxValue))
  1058. {
  1059. recalculateMinMax = true;
  1060. }
  1061. // Modify the correct pixel in our modification buffer.
  1062. pixelToModify = values[index];
  1063. // Potentially expand the min/max range of our image.
  1064. // If these expand, we'll need to recalculate our auto-scale multiplier and offset and refresh the entire image.
  1065. m_minValue = AZStd::min(m_minValue, values[index]);
  1066. m_maxValue = AZStd::max(m_maxValue, values[index]);
  1067. // Track that we've modified the image
  1068. m_imageIsModified = true;
  1069. }
  1070. }
  1071. if (recalculateMinMax)
  1072. {
  1073. // We've potentially shrunk our min/max range, so recalculate the min/max values, multiplier, and offset.
  1074. // We'll do this before the next check, which checks to see if our min/max range has changed.
  1075. SetupAutoScaleMultiplierAndOffset();
  1076. }
  1077. // If our modifications have either expanded or contracted our min/max range, recalculate the scaling multiplier
  1078. // and offset and refresh the entire image.
  1079. if ((preModificationMinValue != m_minValue) || (preModificationMaxValue != m_maxValue))
  1080. {
  1081. SetupMultiplierAndOffset(m_minValue, m_maxValue);
  1082. refreshEntireImage = true;
  1083. }
  1084. }
  1085. else
  1086. {
  1087. // If we're set to manual scale or no scale, modifications are just a simple write loop.
  1088. for (size_t index = 0; index < positions.size(); index++)
  1089. {
  1090. if (PixelIndexIsValid(positions[index]))
  1091. {
  1092. const auto& [x, y] = positions[index];
  1093. // Modify the correct pixel in our modification buffer.
  1094. m_modifiedImageData[(y * width) + x] = values[index];
  1095. // Track that we've modified the image
  1096. m_imageIsModified = true;
  1097. }
  1098. }
  1099. }
  1100. }
  1101. // If we need to refresh the entire image, send the notification outside of the scope block containing the queryMutex lock
  1102. // to avoid any potential deadlocks.
  1103. if (refreshEntireImage)
  1104. {
  1105. LmbrCentral::DependencyNotificationBus::Event(
  1106. GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  1107. }
  1108. }
  1109. }