EditorImageGradientComponent.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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 <Editor/EditorImageGradientComponent.h>
  9. #include <AzToolsFramework/API/EntityCompositionNotificationBus.h>
  10. #include <GradientSignal/Editor/EditorGradientImageCreatorUtils.h>
  11. namespace GradientSignal
  12. {
  13. // Implements EditorImageGradientComponent RTTI functions
  14. AZ_RTTI_NO_TYPE_INFO_IMPL(EditorImageGradientComponent, AzToolsFramework::Components::EditorComponentBase);
  15. void EditorImageGradientComponent::Reflect(AZ::ReflectContext* context)
  16. {
  17. EditorImageGradientComponentMode::Reflect(context);
  18. if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  19. {
  20. serializeContext->Class<EditorImageGradientComponent, AzToolsFramework::Components::EditorComponentBase>()
  21. ->Version(4)
  22. ->Field("Previewer", &EditorImageGradientComponent::m_previewer)
  23. ->Field("Configuration", &EditorImageGradientComponent::m_configuration)
  24. ->Field("PaintableImageAssetHelper", &EditorImageGradientComponent::m_paintableImageAssetHelper)
  25. ;
  26. if (auto editContext = serializeContext->GetEditContext())
  27. {
  28. editContext->Class<ImageGradientConfig>("Image Gradient", "")
  29. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  30. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  31. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  32. ->DataElement(AZ::Edit::UIHandlers::Default, &ImageGradientConfig::m_imageAsset,
  33. "Image Asset", "Image asset whose values will be mapped as gradient output.")
  34. ->Attribute(AZ::Edit::Attributes::Handler, AZ_CRC_CE("GradientSignalStreamingImageAsset"))
  35. ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &ImageGradientConfig::GetImageAssetPropertyName)
  36. ->Attribute(AZ::Edit::Attributes::ReadOnly, &ImageGradientConfig::IsImageAssetReadOnly)
  37. // Refresh the attributes because some fields will switch between read-only and writeable when
  38. // the image asset is changed.
  39. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
  40. ->DataElement(AZ::Edit::UIHandlers::ComboBox, &ImageGradientConfig::m_samplingType,
  41. "Sampling Type", "Sampling type to use for the image data.")
  42. ->EnumAttribute(SamplingType::Point, "Point")
  43. ->EnumAttribute(SamplingType::Bilinear, "Bilinear")
  44. ->EnumAttribute(SamplingType::Bicubic, "Bicubic")
  45. ->Attribute(AZ::Edit::Attributes::ReadOnly, &ImageGradientConfig::AreImageOptionsReadOnly)
  46. ->DataElement(AZ::Edit::UIHandlers::Vector2, &ImageGradientConfig::m_tiling,
  47. "Tiling", "Number of times to tile horizontally/vertically.")
  48. ->Attribute(AZ::Edit::Attributes::Min, 0.01f)
  49. ->Attribute(AZ::Edit::Attributes::SoftMin, 1.0f)
  50. ->Attribute(AZ::Edit::Attributes::Max, std::numeric_limits<float>::max())
  51. ->Attribute(AZ::Edit::Attributes::SoftMax, 1024.0f)
  52. ->Attribute(AZ::Edit::Attributes::Step, 0.25f)
  53. ->Attribute(AZ::Edit::Attributes::ReadOnly, &ImageGradientConfig::AreImageOptionsReadOnly)
  54. ->DataElement(AZ::Edit::UIHandlers::ComboBox, &ImageGradientConfig::m_channelToUse,
  55. "Channel To Use", "The channel to use from the image.")
  56. ->EnumAttribute(ChannelToUse::Red, "Red")
  57. ->EnumAttribute(ChannelToUse::Green, "Green")
  58. ->EnumAttribute(ChannelToUse::Blue, "Blue")
  59. ->EnumAttribute(ChannelToUse::Alpha, "Alpha")
  60. ->EnumAttribute(ChannelToUse::Terrarium, "Terrarium")
  61. ->Attribute(AZ::Edit::Attributes::ReadOnly, &ImageGradientConfig::AreImageOptionsReadOnly)
  62. ->DataElement(
  63. AZ::Edit::UIHandlers::Slider, &ImageGradientConfig::m_mipIndex, "Mip Index", "Mip index to sample from.")
  64. ->Attribute(AZ::Edit::Attributes::Min, 0)
  65. ->Attribute(AZ::Edit::Attributes::Max, AZ::RHI::Limits::Image::MipCountMax)
  66. ->Attribute(AZ::Edit::Attributes::ReadOnly, &ImageGradientConfig::AreImageOptionsReadOnly)
  67. ->DataElement(AZ::Edit::UIHandlers::ComboBox, &ImageGradientConfig::m_customScaleType,
  68. "Custom Scale", "Choose a type of scaling to be applied to the image data.")
  69. ->EnumAttribute(CustomScaleType::None, "None")
  70. ->EnumAttribute(CustomScaleType::Auto, "Auto")
  71. ->EnumAttribute(CustomScaleType::Manual, "Manual")
  72. // Refresh the entire tree on scaling changes, because it will show/hide the scale ranges for Manual scaling.
  73. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
  74. ->Attribute(AZ::Edit::Attributes::ReadOnly, &ImageGradientConfig::AreImageOptionsReadOnly)
  75. ->DataElement(AZ::Edit::UIHandlers::Default, &ImageGradientConfig::m_scaleRangeMin,
  76. "Range Minimum", "The minimum range each value from the image data is scaled against.")
  77. ->Attribute(AZ::Edit::Attributes::Visibility, &ImageGradientConfig::GetManualScaleVisibility)
  78. ->Attribute(AZ::Edit::Attributes::ReadOnly, &ImageGradientConfig::AreImageOptionsReadOnly)
  79. ->DataElement(AZ::Edit::UIHandlers::Default, &ImageGradientConfig::m_scaleRangeMax,
  80. "Range Maximum", "The maximum range each value from the image data is scaled against.")
  81. ->Attribute(AZ::Edit::Attributes::Visibility, &ImageGradientConfig::GetManualScaleVisibility)
  82. ->Attribute(AZ::Edit::Attributes::ReadOnly, &ImageGradientConfig::AreImageOptionsReadOnly)
  83. ;
  84. editContext
  85. ->Class<EditorImageGradientComponent>(
  86. EditorImageGradientComponent::s_componentName, EditorImageGradientComponent::s_componentDescription)
  87. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  88. ->Attribute(AZ::Edit::Attributes::Icon, EditorImageGradientComponent::s_icon)
  89. ->Attribute(AZ::Edit::Attributes::ViewportIcon, EditorImageGradientComponent::s_viewportIcon)
  90. ->Attribute(AZ::Edit::Attributes::HelpPageURL, EditorImageGradientComponent::s_helpUrl)
  91. ->Attribute(AZ::Edit::Attributes::Category, EditorImageGradientComponent::s_categoryName)
  92. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
  93. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  94. ->DataElement(
  95. AZ::Edit::UIHandlers::Default, &EditorImageGradientComponent::m_previewer, "Previewer", "Gradient Previewer")
  96. // Configuration for the Image Gradient control itself
  97. ->DataElement(AZ::Edit::UIHandlers::Default, &EditorImageGradientComponent::m_configuration, "Configuration", "")
  98. ->Attribute(AZ::Edit::Attributes::ReadOnly, &EditorImageGradientComponent::GetImageOptionsReadOnly)
  99. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorImageGradientComponent::ConfigurationChanged)
  100. // Paint controls for editing the image
  101. ->DataElement(AZ::Edit::UIHandlers::Default, &EditorImageGradientComponent::m_paintableImageAssetHelper,
  102. "Paint Image", "Paint into an image asset")
  103. ->Attribute(AZ::Edit::Attributes::ButtonText, "Paint")
  104. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  105. ;
  106. }
  107. }
  108. }
  109. // The following methods pass through to the runtime component so that the Editor component shares the same requirements.
  110. void EditorImageGradientComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  111. {
  112. ImageGradientComponent::GetRequiredServices(services);
  113. }
  114. void EditorImageGradientComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  115. {
  116. ImageGradientComponent::GetIncompatibleServices(services);
  117. }
  118. void EditorImageGradientComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  119. {
  120. ImageGradientComponent::GetProvidedServices(services);
  121. }
  122. void EditorImageGradientComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  123. {
  124. ImageGradientComponent::GetDependentServices(services);
  125. }
  126. void EditorImageGradientComponent::BuildGameEntity(AZ::Entity* gameEntity)
  127. {
  128. // When building the game entity, use the copy of the runtime configuration on the Editor component to create
  129. // a new runtime component that's configured correctly.
  130. gameEntity->AddComponent(aznew ImageGradientComponent(m_configuration));
  131. }
  132. void EditorImageGradientComponent::Init()
  133. {
  134. AzToolsFramework::Components::EditorComponentBase::Init();
  135. // Initialize the copy of the runtime component.
  136. m_runtimeComponentActive = false;
  137. m_component.ReadInConfig(&m_configuration);
  138. m_component.Init();
  139. }
  140. void EditorImageGradientComponent::Activate()
  141. {
  142. // This block of code is aligned with EditorWrappedComponentBase
  143. {
  144. AzToolsFramework::Components::EditorComponentBase::Activate();
  145. // Use the visibility bus to control whether or not the runtime gradient is active and processing in the Editor.
  146. AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusConnect(GetEntityId());
  147. AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
  148. m_visible, GetEntityId(), &AzToolsFramework::EditorEntityInfoRequestBus::Events::IsVisible);
  149. // Synchronize the runtime component with the Editor component.
  150. m_component.ReadInConfig(&m_configuration);
  151. m_component.SetEntity(GetEntity());
  152. if (m_visible)
  153. {
  154. m_component.Activate();
  155. m_runtimeComponentActive = true;
  156. }
  157. }
  158. LmbrCentral::DependencyNotificationBus::Handler::BusConnect(GetEntityId());
  159. AzFramework::PaintBrushNotificationBus::Handler::BusConnect({ GetEntityId(), GetId() });
  160. m_previewer.Activate(GetEntityId());
  161. // Initialize the paintable image asset helper.
  162. m_paintableImageAssetHelper.Activate(
  163. AZ::EntityComponentIdPair(GetEntityId(), GetId()),
  164. OutputFormat::R32,
  165. "Image Asset",
  166. [this]()
  167. {
  168. // Get a default image filename and path that either uses the source asset filename (if the source asset exists)
  169. // or creates a new name by taking the entity name and adding "_gsi.tif".
  170. return AZ::IO::Path(ImageCreatorUtils::GetDefaultImageSourcePath(
  171. m_configuration.m_imageAsset.GetId(), GetEntity()->GetName() + AZStd::string("_gsi.tif")));
  172. },
  173. [this](AZ::Data::Asset<AZ::Data::AssetData> createdAsset)
  174. {
  175. // Set the active image to the created one.
  176. m_component.SetImageAsset(createdAsset);
  177. OnCompositionChanged();
  178. });
  179. AZStd::string assetLabel =
  180. m_paintableImageAssetHelper.Refresh(m_configuration.m_imageAsset);
  181. m_configuration.SetImageAssetPropertyName(assetLabel);
  182. }
  183. void EditorImageGradientComponent::Deactivate()
  184. {
  185. m_paintableImageAssetHelper.Deactivate();
  186. m_previewer.Deactivate();
  187. AzFramework::PaintBrushNotificationBus::Handler::BusDisconnect();
  188. LmbrCentral::DependencyNotificationBus::Handler::BusDisconnect();
  189. // This block of code is aligned with EditorWrappedComponentBase
  190. {
  191. AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusDisconnect();
  192. AzToolsFramework::Components::EditorComponentBase::Deactivate();
  193. m_runtimeComponentActive = false;
  194. m_component.Deactivate();
  195. // remove the entity association, in case the parent component is being removed, otherwise the component will be reactivated
  196. m_component.SetEntity(nullptr);
  197. }
  198. }
  199. void EditorImageGradientComponent::OnEntityVisibilityChanged(bool visibility)
  200. {
  201. if (m_visible != visibility)
  202. {
  203. m_visible = visibility;
  204. ConfigurationChanged();
  205. }
  206. }
  207. void EditorImageGradientComponent::OnCompositionRegionChanged([[maybe_unused]] const AZ::Aabb& dirtyRegion)
  208. {
  209. // If only a region of the image gradient changed, then we only need to refresh the preview.
  210. m_previewer.RefreshPreview();
  211. }
  212. void EditorImageGradientComponent::OnCompositionChanged()
  213. {
  214. // Keep track of what our previous label was, so that we know to refresh if it changes.
  215. // We need to grab this before calling WriteOutConfig() because that will overwrite the label with
  216. // the empty label that's stored with the runtime component.
  217. auto previousImageAssetPropertyName = m_configuration.GetImageAssetPropertyName();
  218. m_previewer.RefreshPreview();
  219. m_component.WriteOutConfig(&m_configuration);
  220. SetDirty();
  221. AZStd::string assetLabel = m_paintableImageAssetHelper.Refresh(m_configuration.m_imageAsset);
  222. m_configuration.SetImageAssetPropertyName(assetLabel);
  223. bool imageNameChanged = m_configuration.GetImageAssetPropertyName() != previousImageAssetPropertyName;
  224. InvalidatePropertyDisplay(imageNameChanged ? AzToolsFramework::Refresh_EntireTree : AzToolsFramework::Refresh_AttributesAndValues);
  225. }
  226. AZ::u32 EditorImageGradientComponent::ConfigurationChanged()
  227. {
  228. // Cancel any pending preview refreshes before locking, to help ensure the preview itself isn't holding the lock
  229. auto entityIds = m_previewer.CancelPreviewRendering();
  230. // This block of code aligns with EditorWrappedComponentBase
  231. {
  232. if (m_runtimeComponentActive)
  233. {
  234. m_runtimeComponentActive = false;
  235. m_component.Deactivate();
  236. }
  237. m_component.ReadInConfig(&m_configuration);
  238. if (m_visible && !m_runtimeComponentActive)
  239. {
  240. m_component.Activate();
  241. m_runtimeComponentActive = true;
  242. }
  243. }
  244. // Refresh any of the previews that we canceled that were still in progress so they can be completed
  245. m_previewer.RefreshPreviews(entityIds);
  246. // This OnCompositionChanged notification will refresh our own preview so we don't need to call RefreshPreview explicitly
  247. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  248. return AZ::Edit::PropertyRefreshLevels::None;
  249. }
  250. bool EditorImageGradientComponent::GetImageOptionsReadOnly() const
  251. {
  252. // you cannot change any configuration option if the image is modified in memory but not saved.
  253. // note that this will apply to all child options, too.
  254. return (m_component.ModificationBufferIsActive());
  255. }
  256. void EditorImageGradientComponent::OnPaintModeBegin()
  257. {
  258. m_configuration.m_numImageModificationsActive++;
  259. // Forward the paint brush notification to the runtime component.
  260. AzFramework::PaintBrushNotificationBus::Event(
  261. { m_component.GetEntityId(), m_component.GetId() }, &AzFramework::PaintBrushNotificationBus::Events::OnPaintModeBegin);
  262. // While we're editing, we need to set all the configuration properties to read-only and refresh them.
  263. // Otherwise, the property changes could conflict with the current painted modifications.
  264. InvalidatePropertyDisplay(AzToolsFramework::Refresh_AttributesAndValues);
  265. }
  266. void EditorImageGradientComponent::OnPaintModeEnd()
  267. {
  268. // Forward the paint brush notification to the runtime component.
  269. AzFramework::PaintBrushNotificationBus::Event(
  270. { m_component.GetEntityId(), m_component.GetId() }, &AzFramework::PaintBrushNotificationBus::Events::OnPaintModeEnd);
  271. m_configuration.m_numImageModificationsActive--;
  272. // It's possible that we're leaving component mode as the result of an "undo" action.
  273. // If that's the case, don't prompt the user to save the changes.
  274. if (!AzToolsFramework::UndoRedoOperationInProgress() && m_component.ImageIsModified())
  275. {
  276. SavePaintedData(); // this function may execute a modal call. Delay property invalidation until afterwards.
  277. }
  278. else
  279. {
  280. m_component.ClearImageModificationBuffer(); // unless we do this, all properties stay read-only
  281. }
  282. // We're done editing, so set all the configuration properties back to writeable and refresh them.
  283. InvalidatePropertyDisplay(AzToolsFramework::Refresh_AttributesAndValues);
  284. }
  285. void EditorImageGradientComponent::OnBrushStrokeBegin(const AZ::Color& color)
  286. {
  287. // Forward the paint brush notification to the runtime component.
  288. AzFramework::PaintBrushNotificationBus::Event(
  289. { m_component.GetEntityId(), m_component.GetId() },
  290. &AzFramework::PaintBrushNotificationBus::Events::OnBrushStrokeBegin,
  291. color);
  292. }
  293. void EditorImageGradientComponent::OnBrushStrokeEnd()
  294. {
  295. // Forward the paint brush notification to the runtime component.
  296. AzFramework::PaintBrushNotificationBus::Event(
  297. { m_component.GetEntityId(), m_component.GetId() }, &AzFramework::PaintBrushNotificationBus::Events::OnBrushStrokeEnd);
  298. }
  299. void EditorImageGradientComponent::OnPaint(
  300. const AZ::Color& color, const AZ::Aabb& dirtyArea, ValueLookupFn& valueLookupFn, BlendFn& blendFn)
  301. {
  302. // Forward the paint brush notification to the runtime component.
  303. AzFramework::PaintBrushNotificationBus::Event(
  304. { m_component.GetEntityId(), m_component.GetId() }, &AzFramework::PaintBrushNotificationBus::Events::OnPaint,
  305. color, dirtyArea, valueLookupFn, blendFn);
  306. }
  307. void EditorImageGradientComponent::OnSmooth(
  308. const AZ::Color& color,
  309. const AZ::Aabb& dirtyArea,
  310. ValueLookupFn& valueLookupFn,
  311. AZStd::span<const AZ::Vector3> valuePointOffsets,
  312. SmoothFn& smoothFn)
  313. {
  314. // Forward the paint brush notification to the runtime component.
  315. AzFramework::PaintBrushNotificationBus::Event(
  316. { m_component.GetEntityId(), m_component.GetId() }, &AzFramework::PaintBrushNotificationBus::Events::OnSmooth,
  317. color, dirtyArea, valueLookupFn, valuePointOffsets, smoothFn);
  318. }
  319. AZ::Color EditorImageGradientComponent::OnGetColor(const AZ::Vector3& brushCenter) const
  320. {
  321. AZ::Color result;
  322. // Forward the paint brush notification to the runtime component.
  323. AzFramework::PaintBrushNotificationBus::EventResult(result,
  324. { m_component.GetEntityId(), m_component.GetId() },
  325. &AzFramework::PaintBrushNotificationBus::Events::OnGetColor,
  326. brushCenter);
  327. return result;
  328. }
  329. bool EditorImageGradientComponent::SavePaintedData()
  330. {
  331. // Get the resolution of our modified image.
  332. const int imageResolutionX = aznumeric_cast<int>(m_component.GetImageWidth());
  333. const int imageResolutionY = aznumeric_cast<int>(m_component.GetImageHeight());
  334. // Get the image modification buffer
  335. auto pixelBuffer = m_component.GetImageModificationBuffer();
  336. const OutputFormat format = OutputFormat::R32;
  337. auto createdAsset = m_paintableImageAssetHelper.SaveImage(
  338. imageResolutionX, imageResolutionY, format,
  339. AZStd::span<const uint8_t>(reinterpret_cast<uint8_t*>(pixelBuffer->data()), pixelBuffer->size() * sizeof(float)));
  340. if (createdAsset)
  341. {
  342. // Set the active image to the created one.
  343. m_component.SetImageAsset(createdAsset.value());
  344. m_component.ClearImageModificationBuffer(); // we no longer have modified changes that are unsaved.
  345. OnCompositionChanged();
  346. }
  347. return createdAsset.has_value();
  348. }
  349. }