EditorGradientBakerComponent.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  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 <AzCore/IO/SystemFile.h>
  9. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  10. #include <AzToolsFramework/UI/PropertyEditor/PropertyFilePathCtrl.h>
  11. #include <GradientSignal/Ebuses/GradientPreviewRequestBus.h>
  12. #include <GradientSignal/Ebuses/ImageGradientRequestBus.h>
  13. #include <GradientSignal/Editor/EditorGradientBakerComponent.h>
  14. #include <GradientSignal/Editor/EditorGradientImageCreatorUtils.h>
  15. namespace GradientSignal
  16. {
  17. // Custom AZ::Job so that we can bake the output image asynchronously.
  18. // We create the AZ::Job with isAutoDelete = false so that we can detect
  19. // when the job has completed, which means we need to handle its deletion.
  20. BakeImageJob::BakeImageJob(
  21. const GradientBakerConfig& configuration,
  22. const AZ::IO::Path& fullPath,
  23. AZ::Aabb inputBounds,
  24. AZ::EntityId boundsEntityId
  25. )
  26. : AZ::Job(false, nullptr)
  27. , m_configuration(configuration)
  28. , m_outputImageAbsolutePath(fullPath)
  29. , m_inputBounds(inputBounds)
  30. , m_boundsEntityId(boundsEntityId)
  31. {
  32. }
  33. BakeImageJob::~BakeImageJob()
  34. {
  35. // Make sure we don't have anything running on another thread before destroying
  36. // the job instance itself.
  37. CancelAndWait();
  38. }
  39. void BakeImageJob::Process()
  40. {
  41. // Get the actual resolution of our image. Note that this might be non-square, depending on how the window is sized.
  42. const int imageResolutionX = aznumeric_cast<int>(m_configuration.m_outputResolution.GetX());
  43. const int imageResolutionY = aznumeric_cast<int>(m_configuration.m_outputResolution.GetY());
  44. // The TGA and EXR formats aren't recognized with only single channel data,
  45. // so need to use RGBA format for them
  46. int channels = 1;
  47. if (m_outputImageAbsolutePath.Extension() == ".tga" || m_outputImageAbsolutePath.Extension() == ".exr")
  48. {
  49. channels = 4;
  50. }
  51. int bytesPerChannel = ImageCreatorUtils::GetBytesPerChannel(m_configuration.m_outputFormat);
  52. const size_t imageSize = imageResolutionX * imageResolutionY * channels * bytesPerChannel;
  53. AZStd::vector<AZ::u8> pixels(imageSize, 0);
  54. const AZ::Vector3 inputBoundsCenter = m_inputBounds.GetCenter();
  55. const AZ::Vector3 inputBoundsExtentsOld = m_inputBounds.GetExtents();
  56. m_inputBounds =
  57. AZ::Aabb::CreateCenterRadius(inputBoundsCenter, AZ::GetMax(inputBoundsExtentsOld.GetX(), inputBoundsExtentsOld.GetY()) / 2.0f);
  58. const AZ::Vector3 inputBoundsStart =
  59. AZ::Vector3(m_inputBounds.GetMin().GetX(), m_inputBounds.GetMin().GetY(), inputBoundsCenter.GetZ());
  60. const AZ::Vector3 inputBoundsExtents = m_inputBounds.GetExtents();
  61. const float inputBoundsExtentsX = inputBoundsExtents.GetX();
  62. const float inputBoundsExtentsY = inputBoundsExtents.GetY();
  63. // When sampling the gradient, we can choose to either do it at the corners of each texel area we're sampling, or at the center.
  64. // They're both correct choices in different ways. We're currently choosing to do the corners, which makes scaledTexelOffset = 0,
  65. // but the math is here to make it easy to change later if we ever decide sampling from the center provides a more intuitive
  66. // image.
  67. constexpr float texelOffset = 0.0f; // Use 0.5f to sample from the center of the texel.
  68. const AZ::Vector3 scaledTexelOffset(
  69. texelOffset * inputBoundsExtentsX / static_cast<float>(imageResolutionX),
  70. texelOffset * inputBoundsExtentsY / static_cast<float>(imageResolutionY), 0.0f);
  71. // Scale from our image size space (ex: 256 pixels) to our bounds space (ex: 16 meters)
  72. const AZ::Vector3 pixelToBoundsScale(
  73. inputBoundsExtentsX / static_cast<float>(imageResolutionX), inputBoundsExtentsY / static_cast<float>(imageResolutionY), 0.0f);
  74. const AZ::Vector3 positionOffset = inputBoundsStart + scaledTexelOffset;
  75. // Generate a set of input positions that are inside the bounds along with
  76. // their corresponding x,y indices
  77. AZStd::vector<AZ::Vector3> inputPositions;
  78. inputPositions.reserve(imageResolutionX * imageResolutionY);
  79. AZStd::vector<AZStd::pair<int, int>> indices;
  80. indices.reserve(imageResolutionX * imageResolutionY);
  81. // All the input position gathering logic occurs in this lambda passed to the
  82. // ShapeComponentRequestsBus so that we only need one bus call
  83. LmbrCentral::ShapeComponentRequestsBus::Event(
  84. m_boundsEntityId,
  85. [this, positionOffset, pixelToBoundsScale, imageResolutionX, imageResolutionY, &inputPositions, &indices](LmbrCentral::ShapeComponentRequestsBus::Events* shape)
  86. {
  87. for (int y = 0; !m_shouldCancel && (y < imageResolutionY); ++y)
  88. {
  89. for (int x = 0; x < imageResolutionX; ++x)
  90. {
  91. // Invert world y to match axis. (We use "imageBoundsY- 1" to invert because our loop doesn't go all the way to
  92. // imageBoundsY)
  93. AZ::Vector3 uvw(static_cast<float>(x), static_cast<float>((imageResolutionY - 1) - y), 0.0f);
  94. AZ::Vector3 position = positionOffset + (uvw * pixelToBoundsScale);
  95. if (!shape->IsPointInside(position))
  96. {
  97. continue;
  98. }
  99. // Keep track of this input position + the x,y indices
  100. inputPositions.push_back(position);
  101. indices.push_back(AZStd::make_pair(x, y));
  102. }
  103. }
  104. });
  105. // Retrieve all the gradient values for the input positions
  106. const size_t numPositions = inputPositions.size();
  107. AZStd::vector<float> outputValues(numPositions);
  108. m_configuration.m_gradientSampler.GetValues(inputPositions, outputValues);
  109. // Write out all the gradient values to our output image
  110. for (int i = 0; !m_shouldCancel && (i < numPositions); ++i)
  111. {
  112. const float& sample = outputValues[i];
  113. const auto& [x, y] = indices[i];
  114. // Write out the sample value for the pixel based on output format
  115. int index = ((y * imageResolutionX) + x) * channels;
  116. switch (m_configuration.m_outputFormat)
  117. {
  118. case OutputFormat::R8:
  119. {
  120. AZ::u8 value = static_cast<AZ::u8>(sample * std::numeric_limits<AZ::u8>::max());
  121. pixels[index] = value; // R
  122. if (channels == 4)
  123. {
  124. pixels[index + 1] = value; // G
  125. pixels[index + 2] = value; // B
  126. pixels[index + 3] = std::numeric_limits<AZ::u8>::max(); // A
  127. }
  128. break;
  129. }
  130. case OutputFormat::R16:
  131. {
  132. auto actualMem = reinterpret_cast<AZ::u16*>(pixels.data());
  133. AZ::u16 value = static_cast<AZ::u16>(sample * std::numeric_limits<AZ::u16>::max());
  134. actualMem[index] = value; // R
  135. if (channels == 4)
  136. {
  137. actualMem[index + 1] = value; // G
  138. actualMem[index + 2] = value; // B
  139. actualMem[index + 3] = std::numeric_limits<AZ::u16>::max(); // A
  140. }
  141. break;
  142. }
  143. case OutputFormat::R32:
  144. {
  145. auto actualMem = reinterpret_cast<float*>(pixels.data());
  146. actualMem[index] = sample; // R
  147. if (channels == 4)
  148. {
  149. actualMem[index + 1] = sample; // G
  150. actualMem[index + 2] = sample; // B
  151. actualMem[index + 3] = 1.0f; // A
  152. }
  153. break;
  154. }
  155. }
  156. }
  157. // Don't try to write out the image if the job was canceled
  158. if (!m_shouldCancel)
  159. {
  160. constexpr bool showProgressDialog = false;
  161. bool result = ImageCreatorUtils::WriteImage(
  162. m_outputImageAbsolutePath.c_str(),
  163. imageResolutionX, imageResolutionY, channels, m_configuration.m_outputFormat, pixels,
  164. showProgressDialog);
  165. if (!result)
  166. {
  167. AZ_Error(
  168. "GradientBaker", result, "Failed to write out gradient baked image to path: %s", m_outputImageAbsolutePath.c_str());
  169. }
  170. }
  171. // Safely notify that the job has finished
  172. // The m_finishedNotify is used to notify our blocking wait to cancel the job
  173. // while it was running
  174. {
  175. AZStd::lock_guard<decltype(m_bakeImageMutex)> lock(m_bakeImageMutex);
  176. m_shouldCancel = false;
  177. m_isFinished.store(true);
  178. m_finishedNotify.notify_all();
  179. }
  180. }
  181. void BakeImageJob::CancelAndWait()
  182. {
  183. // Set an atomic bool that the Process() loop checks on each iteration to see
  184. // if it should cancel baking the image
  185. m_shouldCancel = true;
  186. // Then we synchronously block until the job has completed
  187. Wait();
  188. }
  189. void BakeImageJob::Wait()
  190. {
  191. // Jobs don't inherently have a way to block on cancellation / completion, so we need to implement it
  192. // ourselves.
  193. // If we've already started the job, block on a condition variable that gets notified at
  194. // the end of the Process() function.
  195. AZStd::unique_lock<decltype(m_bakeImageMutex)> lock(m_bakeImageMutex);
  196. if (!m_isFinished)
  197. {
  198. m_finishedNotify.wait(lock, [this] { return m_isFinished == true; });
  199. }
  200. // Regardless of whether or not we were running, we need to reset the internal Job class status
  201. // and clear our cancel flag.
  202. Reset(true);
  203. m_shouldCancel = false;
  204. }
  205. bool BakeImageJob::IsFinished() const
  206. {
  207. return m_isFinished.load();
  208. }
  209. void GradientBakerConfig::Reflect(AZ::ReflectContext* context)
  210. {
  211. AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
  212. if (serialize)
  213. {
  214. serialize->Class<GradientBakerConfig, AZ::ComponentConfig>()
  215. ->Version(2)
  216. ->Field("Gradient", &GradientBakerConfig::m_gradientSampler)
  217. ->Field("InputBounds", &GradientBakerConfig::m_inputBounds)
  218. ->Field("OutputResolution", &GradientBakerConfig::m_outputResolution)
  219. ->Field("OutputFormat", &GradientBakerConfig::m_outputFormat)
  220. ->Field("OutputImagePath", &GradientBakerConfig::m_outputImagePath)
  221. ;
  222. AZ::EditContext* edit = serialize->GetEditContext();
  223. if (edit)
  224. {
  225. edit->Class<GradientBakerConfig>("Gradient Baker", "")
  226. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  227. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  228. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  229. ->DataElement(
  230. AZ::Edit::UIHandlers::Default, &GradientBakerConfig::m_gradientSampler, "Gradient",
  231. "Input gradient to bake the output image from.")
  232. ->DataElement(
  233. AZ::Edit::UIHandlers::Default, &GradientBakerConfig::m_inputBounds, "Input Bounds",
  234. "Input bounds for where to sample the data.")
  235. ->DataElement(
  236. AZ::Edit::UIHandlers::Default, &GradientBakerConfig::m_outputResolution, "Resolution",
  237. "Output resolution of the baked image.")
  238. ->Attribute(AZ::Edit::Attributes::Decimals, 0)
  239. ->Attribute(AZ::Edit::Attributes::Min, 1.0f)
  240. ->DataElement(
  241. AZ::Edit::UIHandlers::ComboBox, &GradientBakerConfig::m_outputFormat, "Output Format",
  242. "Output format of the baked image.")
  243. ->Attribute(AZ::Edit::Attributes::EnumValues, &ImageCreatorUtils::SupportedOutputFormatOptions)
  244. ->DataElement(
  245. AZ::Edit::UIHandlers::Default, &GradientBakerConfig::m_outputImagePath, "Output Path",
  246. "Output path to bake the image to.")
  247. ->Attribute(AZ::Edit::Attributes::SourceAssetFilterPattern, ImageCreatorUtils::GetSupportedImagesFilter())
  248. ->Attribute(AZ::Edit::Attributes::DefaultAsset, "baked_output_gsi")
  249. ;
  250. }
  251. }
  252. }
  253. void EditorGradientBakerComponent::Reflect(AZ::ReflectContext* context)
  254. {
  255. GradientBakerConfig::Reflect(context);
  256. if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  257. {
  258. serializeContext->Class<EditorGradientBakerComponent, EditorComponentBase>()
  259. ->Version(1)
  260. ->Field("Previewer", &EditorGradientBakerComponent::m_previewer)
  261. ->Field("Configuration", &EditorGradientBakerComponent::m_configuration)
  262. ;
  263. if (auto editContext = serializeContext->GetEditContext())
  264. {
  265. editContext
  266. ->Class<EditorGradientBakerComponent>(
  267. EditorGradientBakerComponent::s_componentName, EditorGradientBakerComponent::s_componentDescription)
  268. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  269. ->Attribute(AZ::Edit::Attributes::Icon, s_icon)
  270. ->Attribute(AZ::Edit::Attributes::ViewportIcon, s_viewportIcon)
  271. ->Attribute(AZ::Edit::Attributes::HelpPageURL, s_helpUrl)
  272. ->Attribute(AZ::Edit::Attributes::Category, EditorGradientBakerComponent::s_categoryName)
  273. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
  274. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  275. ->DataElement(AZ::Edit::UIHandlers::Default, &EditorGradientBakerComponent::m_previewer, "Previewer", "")
  276. ->DataElement(AZ::Edit::UIHandlers::Default, &EditorGradientBakerComponent::m_configuration, "Configuration", "")
  277. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorGradientBakerComponent::OnConfigurationChanged)
  278. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  279. ->UIElement(AZ::Edit::UIHandlers::Button, "BakeImage", "Bakes the inbound gradient signal to an image asset")
  280. ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "")
  281. ->Attribute(AZ::Edit::Attributes::ButtonText, "Bake image")
  282. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorGradientBakerComponent::BakeImage)
  283. ->Attribute(AZ::Edit::Attributes::ReadOnly, &EditorGradientBakerComponent::IsBakeDisabled)
  284. ;
  285. }
  286. }
  287. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  288. {
  289. behaviorContext->EBus<GradientImageCreatorRequestBus>("GradientImageCreatorRequestBus")
  290. ->Attribute(AZ::Script::Attributes::Category, "Gradient")
  291. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  292. ->Attribute(AZ::Script::Attributes::Module, "gradient")
  293. ->Event("GetOutputResolution", &GradientImageCreatorRequests::GetOutputResolution)
  294. ->Event("SetOutputResolution", &GradientImageCreatorRequests::SetOutputResolution)
  295. ->VirtualProperty("OutputResolution", "GetOutputResolution", "SetOutputResolution")
  296. ->Event("GetOutputFormat", &GradientImageCreatorRequests::GetOutputFormat)
  297. ->Event("SetOutputFormat", &GradientImageCreatorRequests::SetOutputFormat)
  298. ->VirtualProperty("OutputFormat", "GetOutputFormat", "SetOutputFormat")
  299. ->Event("GetOutputImagePath", &GradientImageCreatorRequests::GetOutputImagePath)
  300. ->Event("SetOutputImagePath", &GradientImageCreatorRequests::SetOutputImagePath)
  301. ->VirtualProperty("OutputImagePath", "GetOutputImagePath", "SetOutputImagePath");
  302. behaviorContext->Class<EditorGradientBakerComponent>()
  303. ->RequestBus("GradientImageCreatorRequestBus")
  304. ->RequestBus("GradientBakerRequestBus")
  305. ;
  306. behaviorContext->EBus<GradientBakerRequestBus>("GradientBakerRequestBus")
  307. ->Attribute(AZ::Script::Attributes::Category, "Gradient")
  308. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  309. ->Attribute(AZ::Script::Attributes::Module, "gradient")
  310. ->Event("BakeImage", &GradientBakerRequests::BakeImage)
  311. ->Event("GetInputBounds", &GradientBakerRequests::GetInputBounds)
  312. ->Event("SetInputBounds", &GradientBakerRequests::SetInputBounds)
  313. ->VirtualProperty("InputBounds", "GetInputBounds", "SetInputBounds");
  314. }
  315. }
  316. void EditorGradientBakerComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  317. {
  318. services.push_back(AZ_CRC_CE("GradientImageCreatorService"));
  319. services.push_back(AZ_CRC_CE("GradientBakerService"));
  320. }
  321. void EditorGradientBakerComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  322. {
  323. services.push_back(AZ_CRC_CE("GradientImageCreatorService"));
  324. services.push_back(AZ_CRC_CE("GradientBakerService"));
  325. }
  326. void EditorGradientBakerComponent::Activate()
  327. {
  328. AzToolsFramework::Components::EditorComponentBase::Activate();
  329. LmbrCentral::DependencyNotificationBus::Handler::BusConnect(GetEntityId());
  330. m_configuration.m_gradientSampler.m_ownerEntityId = GetEntityId();
  331. // Validation needs to happen after the ownerEntity is set in case the validation needs that data
  332. if (!m_configuration.m_gradientSampler.ValidateGradientEntityId())
  333. {
  334. SetDirty();
  335. }
  336. // Setup the dependency monitor and listen for gradient requests
  337. SetupDependencyMonitor();
  338. GradientBakerRequestBus::Handler::BusConnect(GetEntityId());
  339. GradientImageCreatorRequestBus::Handler::BusConnect(GetEntityId());
  340. m_previewer.SetPreviewSettingsVisible(false);
  341. m_previewer.SetPreviewEntity(m_configuration.m_inputBounds);
  342. m_previewer.Activate(GetEntityId());
  343. // If we have a valid output image path set and the other criteria for baking
  344. // are met but the image doesn't exist, then bake it when we activate our component.
  345. if (!IsBakeDisabled())
  346. {
  347. AZ::IO::Path fullPathIO = AzToolsFramework::GetAbsolutePathFromRelativePath(m_configuration.m_outputImagePath);
  348. if (!AZ::IO::SystemFile::Exists(fullPathIO.c_str()))
  349. {
  350. // Delay actually starting the bake until the next tick to
  351. // make sure everything is ready
  352. AZ::TickBus::Handler::BusConnect();
  353. }
  354. }
  355. }
  356. void EditorGradientBakerComponent::Deactivate()
  357. {
  358. // Disconnect from GradientRequestBus first to ensure no queries are in process when deactivating.
  359. GradientRequestBus::Handler::BusDisconnect();
  360. GradientImageCreatorRequestBus::Handler::BusDisconnect();
  361. GradientBakerRequestBus::Handler::BusDisconnect();
  362. m_dependencyMonitor.Reset();
  363. m_previewer.Deactivate();
  364. // If we had a bake job running, delete it before deactivating
  365. // This delete will cancel the job and block waiting for it to complete
  366. AZ::TickBus::Handler::BusDisconnect();
  367. if (m_bakeImageJob)
  368. {
  369. delete m_bakeImageJob;
  370. m_bakeImageJob = nullptr;
  371. }
  372. LmbrCentral::DependencyNotificationBus::Handler::BusDisconnect();
  373. AzToolsFramework::Components::EditorComponentBase::Deactivate();
  374. }
  375. void EditorGradientBakerComponent::OnCompositionChanged()
  376. {
  377. m_previewer.SetPreviewEntity(m_configuration.m_inputBounds);
  378. m_previewer.RefreshPreview();
  379. InvalidatePropertyDisplay(AzToolsFramework::Refresh_AttributesAndValues);
  380. }
  381. void EditorGradientBakerComponent::SetupDependencyMonitor()
  382. {
  383. GradientRequestBus::Handler::BusDisconnect();
  384. m_dependencyMonitor.Reset();
  385. m_dependencyMonitor.ConnectOwner(GetEntityId());
  386. m_dependencyMonitor.ConnectDependency(m_configuration.m_gradientSampler.m_gradientId);
  387. // Connect to GradientRequestBus after the gradient sampler and dependency monitor is configured
  388. // before listening for gradient queries.
  389. GradientRequestBus::Handler::BusConnect(GetEntityId());
  390. }
  391. void EditorGradientBakerComponent::BakeImage()
  392. {
  393. if (IsBakeDisabled())
  394. {
  395. return;
  396. }
  397. AZ::TickBus::Handler::BusConnect();
  398. StartBakeImageJob();
  399. }
  400. void EditorGradientBakerComponent::StartBakeImageJob()
  401. {
  402. // Get the absolute path for our stored relative path
  403. AZ::IO::Path fullPathIO = AzToolsFramework::GetAbsolutePathFromRelativePath(m_configuration.m_outputImagePath);
  404. // Delete the output image (if it exists) before we start baking so that in case
  405. // the Editor shuts down mid-bake we don't leave the output image in a bad state.
  406. if (AZ::IO::SystemFile::Exists(fullPathIO.c_str()))
  407. {
  408. AZ::IO::SystemFile::Delete(fullPathIO.c_str());
  409. }
  410. m_bakeImageJob = aznew BakeImageJob(m_configuration, fullPathIO, m_previewer.GetPreviewBounds(), m_configuration.m_inputBounds);
  411. m_bakeImageJob->Start();
  412. // Force a refresh now so the bake button gets disabled
  413. InvalidatePropertyDisplay(AzToolsFramework::Refresh_AttributesAndValues);
  414. }
  415. bool EditorGradientBakerComponent::IsBakeDisabled() const
  416. {
  417. return m_configuration.m_outputImagePath.empty() || !m_configuration.m_gradientSampler.m_gradientId.IsValid() ||
  418. !m_configuration.m_inputBounds.IsValid() || m_bakeImageJob;
  419. }
  420. void EditorGradientBakerComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
  421. {
  422. if (m_bakeImageJob && m_bakeImageJob->IsFinished())
  423. {
  424. delete m_bakeImageJob;
  425. m_bakeImageJob = nullptr;
  426. AZ::TickBus::Handler::BusDisconnect();
  427. // After a successful bake, if the Entity that contains this gradient baker component also
  428. // has an image gradient component, then update the image gradient's image asset with the
  429. // output path that we baked to
  430. if (GradientSignal::ImageGradientRequestBus::HasHandlers(GetEntityId()))
  431. {
  432. AzToolsFramework::ScopedUndoBatch undo("Update Image Gradient Asset");
  433. GradientSignal::ImageGradientRequestBus::Event(
  434. GetEntityId(), &GradientSignal::ImageGradientRequests::SetImageAssetSourcePath, m_configuration.m_outputImagePath.c_str());
  435. undo.MarkEntityDirty(GetEntityId());
  436. }
  437. // Refresh once the job has completed so the Bake button can be re-enabled
  438. InvalidatePropertyDisplay(AzToolsFramework::Refresh_AttributesAndValues);
  439. }
  440. else if (!m_bakeImageJob)
  441. {
  442. // If we didn't have a bake job already going, start one now
  443. // This is to handle the case where the bake is initiated when
  444. // activating the component and the output image doesn't exist
  445. StartBakeImageJob();
  446. }
  447. }
  448. float EditorGradientBakerComponent::GetValue(const GradientSampleParams& sampleParams) const
  449. {
  450. return m_configuration.m_gradientSampler.GetValue(sampleParams);
  451. }
  452. void EditorGradientBakerComponent::GetValues(AZStd::span<const AZ::Vector3> positions, AZStd::span<float> outValues) const
  453. {
  454. m_configuration.m_gradientSampler.GetValues(positions, outValues);
  455. }
  456. bool EditorGradientBakerComponent::IsEntityInHierarchy(const AZ::EntityId& entityId) const
  457. {
  458. return m_configuration.m_gradientSampler.IsEntityInHierarchy(entityId);
  459. }
  460. AZ::EntityId EditorGradientBakerComponent::GetInputBounds() const
  461. {
  462. return m_configuration.m_inputBounds;
  463. }
  464. void EditorGradientBakerComponent::SetInputBounds(const AZ::EntityId& inputBounds)
  465. {
  466. m_configuration.m_inputBounds = inputBounds;
  467. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  468. }
  469. AZ::Vector2 EditorGradientBakerComponent::GetOutputResolution() const
  470. {
  471. return m_configuration.m_outputResolution;
  472. }
  473. void EditorGradientBakerComponent::SetOutputResolution(const AZ::Vector2& resolution)
  474. {
  475. m_configuration.m_outputResolution = resolution;
  476. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  477. }
  478. OutputFormat EditorGradientBakerComponent::GetOutputFormat() const
  479. {
  480. return m_configuration.m_outputFormat;
  481. }
  482. void EditorGradientBakerComponent::SetOutputFormat(OutputFormat outputFormat)
  483. {
  484. m_configuration.m_outputFormat = outputFormat;
  485. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  486. }
  487. AZ::IO::Path EditorGradientBakerComponent::GetOutputImagePath() const
  488. {
  489. return m_configuration.m_outputImagePath;
  490. }
  491. void EditorGradientBakerComponent::SetOutputImagePath(const AZ::IO::Path& outputImagePath)
  492. {
  493. m_configuration.m_outputImagePath = outputImagePath;
  494. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  495. }
  496. void EditorGradientBakerComponent::OnConfigurationChanged()
  497. {
  498. // Cancel any pending preview refreshes before locking, to help ensure the preview itself isn't holding the lock
  499. auto entityIds = m_previewer.CancelPreviewRendering();
  500. // Re-setup the dependency monitor when the configuration changes because the gradient sampler
  501. // could've changed
  502. SetupDependencyMonitor();
  503. // Refresh any of the previews that we canceled that were still in progress so they can be completed
  504. m_previewer.RefreshPreviews(entityIds);
  505. // This OnCompositionChanged notification will refresh our own preview so we don't need to call RefreshPreview explicitly
  506. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  507. }
  508. } // namespace GradientSignal