EditorTerrainMacroMaterialComponent.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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 <Atom/RPI.Edit/Common/AssetUtils.h>
  9. #include <AzCore/Preprocessor/EnumReflectUtils.h>
  10. #include <AzCore/Serialization/EditContext.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  13. #include <AzQtComponents/Components/Widgets/FileDialog.h>
  14. #include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntry.h>
  15. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  16. #include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
  17. #include <AzToolsFramework/UI/PropertyEditor/PropertyFilePathCtrl.h>
  18. #include <Components/TerrainLayerSpawnerComponent.h>
  19. #include <GradientSignal/Editor/EditorGradientImageCreatorUtils.h>
  20. #include <TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.h>
  21. namespace Terrain
  22. {
  23. // Implements EditorTerrainMacroMaterialComponent RTTI functions
  24. AZ_RTTI_NO_TYPE_INFO_IMPL(EditorTerrainMacroMaterialComponent, AzToolsFramework::Components::EditorComponentBase);
  25. void EditorTerrainMacroMaterialComponent::Reflect(AZ::ReflectContext* context)
  26. {
  27. if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  28. {
  29. serializeContext->Class<EditorTerrainMacroMaterialComponent, AzToolsFramework::Components::EditorComponentBase>()
  30. ->Version(3)
  31. ->Field("Configuration", &EditorTerrainMacroMaterialComponent::m_configuration)
  32. ->Field("PaintableImageAssetHelper", &EditorTerrainMacroMaterialComponent::m_paintableMacroColorAssetHelper)
  33. ;
  34. if (auto* editContext = serializeContext->GetEditContext(); editContext)
  35. {
  36. editContext
  37. ->Class<TerrainMacroMaterialConfig>(
  38. "Terrain Macro Material Component", "Provide a terrain macro material for a region of the world")
  39. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  40. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  41. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  42. ->DataElement(
  43. AZ::Edit::UIHandlers::Default, &TerrainMacroMaterialConfig::m_macroColorAsset, "Color Texture",
  44. "Terrain macro color texture for use by any terrain inside the bounding box on this entity.")
  45. ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &TerrainMacroMaterialConfig::GetMacroColorAssetPropertyName)
  46. ->DataElement(
  47. AZ::Edit::UIHandlers::Default, &TerrainMacroMaterialConfig::m_macroNormalAsset, "Normal Texture",
  48. "Texture for defining surface normal direction. These will override normals generated from the geometry.")
  49. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
  50. ->DataElement(
  51. AZ::Edit::UIHandlers::Default, &TerrainMacroMaterialConfig::m_normalFlipX, "Normal Flip X",
  52. "Flip tangent direction for this normal map.")
  53. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainMacroMaterialConfig::NormalMapAttributesAreReadOnly)
  54. ->DataElement(
  55. AZ::Edit::UIHandlers::Default, &TerrainMacroMaterialConfig::m_normalFlipY, "Normal Flip Y",
  56. "Flip bitangent direction for this normal map.")
  57. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainMacroMaterialConfig::NormalMapAttributesAreReadOnly)
  58. ->DataElement(
  59. AZ::Edit::UIHandlers::Slider, &TerrainMacroMaterialConfig::m_normalFactor, "Normal Factor",
  60. "Strength factor for scaling the normal map values.")
  61. ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
  62. ->Attribute(AZ::Edit::Attributes::Max, 10.0f)
  63. ->Attribute(AZ::Edit::Attributes::SoftMin, 0.0f)
  64. ->Attribute(AZ::Edit::Attributes::SoftMax, 2.0f)
  65. ->Attribute(AZ::Edit::Attributes::ReadOnly, &TerrainMacroMaterialConfig::NormalMapAttributesAreReadOnly)
  66. ->DataElement(
  67. AZ::Edit::UIHandlers::Slider, &TerrainMacroMaterialConfig::m_priority, "Priority",
  68. "Defines order macro materials are applied. Larger numbers = higher priority")
  69. ->Attribute(AZ::Edit::Attributes::Min, AreaConstants::s_priorityMin)
  70. ->Attribute(AZ::Edit::Attributes::Max, AreaConstants::s_priorityMax)
  71. ->Attribute(AZ::Edit::Attributes::SoftMin, AreaConstants::s_prioritySoftMin)
  72. ->Attribute(AZ::Edit::Attributes::SoftMax, AreaConstants::s_prioritySoftMax)
  73. ;
  74. editContext
  75. ->Class<EditorTerrainMacroMaterialComponent>(
  76. EditorTerrainMacroMaterialComponent::s_componentName, EditorTerrainMacroMaterialComponent::s_componentDescription)
  77. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  78. ->Attribute(AZ::Edit::Attributes::Icon, EditorTerrainMacroMaterialComponent::s_icon)
  79. ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.o3de.org/docs/user-guide/components/reference/terrain/terrain-macro-material/")
  80. ->Attribute(AZ::Edit::Attributes::ViewportIcon, EditorTerrainMacroMaterialComponent::s_viewportIcon)
  81. ->Attribute(AZ::Edit::Attributes::HelpPageURL, EditorTerrainMacroMaterialComponent::s_helpUrl)
  82. ->Attribute(AZ::Edit::Attributes::Category, EditorTerrainMacroMaterialComponent::s_categoryName)
  83. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
  84. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  85. // Configuration for the Terrain Macro Material
  86. ->DataElement(AZ::Edit::UIHandlers::Default, &EditorTerrainMacroMaterialComponent::m_configuration, "Configuration", "")
  87. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorTerrainMacroMaterialComponent::ConfigurationChanged)
  88. // Create/edit controls for the macro color image
  89. ->DataElement(
  90. AZ::Edit::UIHandlers::Default,
  91. &EditorTerrainMacroMaterialComponent::m_paintableMacroColorAssetHelper,
  92. "Edit Macro Color Image",
  93. "Edit the macro color image asset")
  94. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  95. ;
  96. }
  97. }
  98. }
  99. // The following methods pass through to the runtime component so that the Editor component shares the same requirements.
  100. void EditorTerrainMacroMaterialComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  101. {
  102. TerrainMacroMaterialComponent::GetRequiredServices(services);
  103. }
  104. void EditorTerrainMacroMaterialComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  105. {
  106. TerrainMacroMaterialComponent::GetIncompatibleServices(services);
  107. }
  108. void EditorTerrainMacroMaterialComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
  109. {
  110. TerrainMacroMaterialComponent::GetProvidedServices(services);
  111. }
  112. void EditorTerrainMacroMaterialComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& services)
  113. {
  114. // The TerrainMacroMaterialComponent doesn't currently have any dependent services, so there's nothing to pass the call through.
  115. // TerrainMacroMaterialComponent::GetDependentServices(services);
  116. }
  117. void EditorTerrainMacroMaterialComponent::BuildGameEntity(AZ::Entity* gameEntity)
  118. {
  119. // When building the game entity, use the copy of the runtime configuration on the Editor component to create
  120. // a new runtime component that's configured correctly.
  121. gameEntity->AddComponent(aznew TerrainMacroMaterialComponent(m_configuration));
  122. }
  123. void EditorTerrainMacroMaterialComponent::Init()
  124. {
  125. AzToolsFramework::Components::EditorComponentBase::Init();
  126. // Initialize the copy of the runtime component.
  127. m_runtimeComponentActive = false;
  128. m_component.ReadInConfig(&m_configuration);
  129. m_component.Init();
  130. }
  131. void EditorTerrainMacroMaterialComponent::Activate()
  132. {
  133. // This block of code is aligned with EditorWrappedComponentBase
  134. {
  135. AzToolsFramework::Components::EditorComponentBase::Activate();
  136. // Use the visibility bus to control whether or not the runtime gradient is active and processing in the Editor.
  137. AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusConnect(GetEntityId());
  138. AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
  139. m_visible, GetEntityId(), &AzToolsFramework::EditorEntityInfoRequestBus::Events::IsVisible);
  140. // Synchronize the runtime component with the Editor component.
  141. m_component.ReadInConfig(&m_configuration);
  142. m_component.SetEntity(GetEntity());
  143. if (m_visible)
  144. {
  145. m_component.Activate();
  146. m_runtimeComponentActive = true;
  147. }
  148. }
  149. LmbrCentral::DependencyNotificationBus::Handler::BusConnect(GetEntityId());
  150. AzFramework::PaintBrushNotificationBus::Handler::BusConnect({ GetEntityId(), GetId() });
  151. TerrainMacroMaterialNotificationBus::Handler::BusConnect();
  152. // Initialize the paintable image asset helper.
  153. m_paintableMacroColorAssetHelper.Activate(
  154. AZ::EntityComponentIdPair(GetEntityId(), GetId()),
  155. GradientSignal::OutputFormat::R8G8B8A8,
  156. "Color Texture",
  157. // Default Save Name callback:
  158. [this]()
  159. {
  160. // Get a default image filename and path that either uses the source asset filename (if the source asset exists)
  161. // or creates a new name by taking the entity name and adding ".png".
  162. return AZ::IO::Path(GradientSignal::ImageCreatorUtils::GetDefaultImageSourcePath(
  163. m_component.GetMacroColorAsset().GetId(), GetEntity()->GetName() + AZStd::string(".png")));
  164. },
  165. // On Asset Created callback:
  166. [this](AZ::Data::Asset<AZ::Data::AssetData> createdAsset)
  167. {
  168. // Set the active image to the created one.
  169. m_component.SetMacroColorAsset(createdAsset);
  170. OnCompositionChanged();
  171. });
  172. AZStd::string assetLabel = m_paintableMacroColorAssetHelper.Refresh(m_component.GetMacroColorAsset());
  173. m_configuration.SetMacroColorAssetPropertyName(assetLabel);
  174. }
  175. void EditorTerrainMacroMaterialComponent::Deactivate()
  176. {
  177. m_paintableMacroColorAssetHelper.Deactivate();
  178. TerrainMacroMaterialNotificationBus::Handler::BusDisconnect();
  179. AzFramework::PaintBrushNotificationBus::Handler::BusDisconnect();
  180. LmbrCentral::DependencyNotificationBus::Handler::BusDisconnect();
  181. // This block of code is aligned with EditorWrappedComponentBase
  182. {
  183. AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusDisconnect();
  184. AzToolsFramework::Components::EditorComponentBase::Deactivate();
  185. m_runtimeComponentActive = false;
  186. m_component.Deactivate();
  187. // remove the entity association, in case the parent component is being removed, otherwise the component will be reactivated
  188. m_component.SetEntity(nullptr);
  189. }
  190. }
  191. void EditorTerrainMacroMaterialComponent::OnEntityVisibilityChanged(bool visibility)
  192. {
  193. if (m_visible != visibility)
  194. {
  195. m_visible = visibility;
  196. ConfigurationChanged();
  197. }
  198. }
  199. void EditorTerrainMacroMaterialComponent::OnTerrainMacroMaterialCreated(
  200. AZ::EntityId macroMaterialEntity, [[maybe_unused]] const MacroMaterialData& macroMaterial)
  201. {
  202. // This notification gets broadcast to *all* entities, so make sure it belongs to this one before refreshing.
  203. if (macroMaterialEntity == m_component.GetEntityId())
  204. {
  205. RefreshPaintableAssetStatus();
  206. }
  207. }
  208. void EditorTerrainMacroMaterialComponent::OnTerrainMacroMaterialChanged(
  209. AZ::EntityId macroMaterialEntity, [[maybe_unused]] const MacroMaterialData& macroMaterial)
  210. {
  211. // This notification gets broadcast to *all* entities, so make sure it belongs to this one before refreshing.
  212. if (macroMaterialEntity == m_component.GetEntityId())
  213. {
  214. RefreshPaintableAssetStatus();
  215. }
  216. }
  217. void EditorTerrainMacroMaterialComponent::OnTerrainMacroMaterialDestroyed(AZ::EntityId macroMaterialEntity)
  218. {
  219. // This notification gets broadcast to *all* entities, so make sure it belongs to this one before refreshing.
  220. if (macroMaterialEntity == m_component.GetEntityId())
  221. {
  222. RefreshPaintableAssetStatus();
  223. }
  224. }
  225. void EditorTerrainMacroMaterialComponent::RefreshPaintableAssetStatus()
  226. {
  227. auto imageAssetPropertyName = m_paintableMacroColorAssetHelper.Refresh(m_component.GetMacroColorAsset());
  228. if (imageAssetPropertyName != m_configuration.GetMacroColorAssetPropertyName())
  229. {
  230. m_configuration.SetMacroColorAssetPropertyName(imageAssetPropertyName);
  231. // If the asset status changed and the image asset property is visible, refresh the entire tree so
  232. // that the label change is picked up.
  233. InvalidatePropertyDisplay(AzToolsFramework::Refresh_EntireTree);
  234. }
  235. else
  236. {
  237. InvalidatePropertyDisplay(AzToolsFramework::Refresh_AttributesAndValues);
  238. }
  239. }
  240. void EditorTerrainMacroMaterialComponent::OnCompositionRegionChanged([[maybe_unused]] const AZ::Aabb& dirtyRegion)
  241. {
  242. // If only a region of the entity changed, we don't need to refresh anything.
  243. // We still need to override this callback though or else region notifications will get passed to OnCompositionChanged().
  244. }
  245. void EditorTerrainMacroMaterialComponent::OnCompositionChanged()
  246. {
  247. // On configuration changes, make sure to preserve the current asset property name status.
  248. auto previousMacroColorAssetPropertyName = m_configuration.GetMacroColorAssetPropertyName();
  249. m_component.WriteOutConfig(&m_configuration);
  250. m_configuration.SetMacroColorAssetPropertyName(previousMacroColorAssetPropertyName);
  251. SetDirty();
  252. RefreshPaintableAssetStatus();
  253. }
  254. AZ::u32 EditorTerrainMacroMaterialComponent::ConfigurationChanged()
  255. {
  256. // This block of code aligns with EditorWrappedComponentBase
  257. {
  258. if (m_runtimeComponentActive)
  259. {
  260. m_runtimeComponentActive = false;
  261. m_component.Deactivate();
  262. }
  263. m_component.ReadInConfig(&m_configuration);
  264. if (m_visible && !m_runtimeComponentActive)
  265. {
  266. m_component.Activate();
  267. m_runtimeComponentActive = true;
  268. }
  269. }
  270. // This OnCompositionChanged notification will refresh our own preview so we don't need to call RefreshPreview explicitly
  271. LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
  272. return AZ::Edit::PropertyRefreshLevels::None;
  273. }
  274. void EditorTerrainMacroMaterialComponent::OnPaintModeBegin()
  275. {
  276. // Forward the paint brush notification to the runtime component.
  277. AzFramework::PaintBrushNotificationBus::Event(
  278. { m_component.GetEntityId(), m_component.GetId() }, &AzFramework::PaintBrushNotificationBus::Events::OnPaintModeBegin);
  279. }
  280. void EditorTerrainMacroMaterialComponent::OnPaintModeEnd()
  281. {
  282. // Forward the paint brush notification to the runtime component.
  283. AzFramework::PaintBrushNotificationBus::Event(
  284. { m_component.GetEntityId(), m_component.GetId() }, &AzFramework::PaintBrushNotificationBus::Events::OnPaintModeEnd);
  285. // It's possible that we're leaving component mode as the result of an "undo" action.
  286. // If that's the case, don't prompt the user to save the changes.
  287. if (!AzToolsFramework::UndoRedoOperationInProgress() && m_component.MacroColorImageIsModified())
  288. {
  289. SavePaintedData();
  290. }
  291. }
  292. void EditorTerrainMacroMaterialComponent::OnBrushStrokeBegin(const AZ::Color& color)
  293. {
  294. // Forward the paint brush notification to the runtime component.
  295. AzFramework::PaintBrushNotificationBus::Event(
  296. { m_component.GetEntityId(), m_component.GetId() }, &AzFramework::PaintBrushNotificationBus::Events::OnBrushStrokeBegin, color);
  297. }
  298. void EditorTerrainMacroMaterialComponent::OnBrushStrokeEnd()
  299. {
  300. // Forward the paint brush notification to the runtime component.
  301. AzFramework::PaintBrushNotificationBus::Event(
  302. { m_component.GetEntityId(), m_component.GetId() }, &AzFramework::PaintBrushNotificationBus::Events::OnBrushStrokeEnd);
  303. }
  304. void EditorTerrainMacroMaterialComponent::OnPaint(
  305. const AZ::Color& color, const AZ::Aabb& dirtyArea, ValueLookupFn& valueLookupFn, BlendFn& blendFn)
  306. {
  307. // Forward the paint brush notification to the runtime component.
  308. AzFramework::PaintBrushNotificationBus::Event(
  309. { m_component.GetEntityId(), m_component.GetId() },
  310. &AzFramework::PaintBrushNotificationBus::Events::OnPaint,
  311. color,
  312. dirtyArea,
  313. valueLookupFn,
  314. blendFn);
  315. }
  316. void EditorTerrainMacroMaterialComponent::OnSmooth(
  317. const AZ::Color& color,
  318. const AZ::Aabb& dirtyArea,
  319. ValueLookupFn& valueLookupFn,
  320. AZStd::span<const AZ::Vector3> valuePointOffsets,
  321. SmoothFn& smoothFn)
  322. {
  323. // Forward the paint brush notification to the runtime component.
  324. AzFramework::PaintBrushNotificationBus::Event(
  325. { m_component.GetEntityId(), m_component.GetId() },
  326. &AzFramework::PaintBrushNotificationBus::Events::OnSmooth,
  327. color,
  328. dirtyArea,
  329. valueLookupFn,
  330. valuePointOffsets,
  331. smoothFn);
  332. }
  333. AZ::Color EditorTerrainMacroMaterialComponent::OnGetColor(const AZ::Vector3& brushCenter) const
  334. {
  335. AZ::Color result;
  336. // Forward the paint brush notification to the runtime component.
  337. AzFramework::PaintBrushNotificationBus::EventResult(
  338. result,
  339. { m_component.GetEntityId(), m_component.GetId() },
  340. &AzFramework::PaintBrushNotificationBus::Events::OnGetColor,
  341. brushCenter);
  342. return result;
  343. }
  344. bool EditorTerrainMacroMaterialComponent::SavePaintedData()
  345. {
  346. const GradientSignal::OutputFormat format = GradientSignal::OutputFormat::R8G8B8A8;
  347. // Get the resolution of our modified image.
  348. const auto imageResolution = m_component.GetMacroColorImageSize();
  349. // Get the image modification buffer
  350. auto pixelBuffer = m_component.GetMacroColorImageModificationBuffer();
  351. // The image is stored in memory in linear color space, but the source asset that we write out needs to be in SRGB color space.
  352. AZStd::vector<uint8_t> rawPixelData = ConvertLinearToSrgbGamma(pixelBuffer);
  353. auto createdAsset = m_paintableMacroColorAssetHelper.SaveImage(
  354. imageResolution.m_width, imageResolution.m_height, format, rawPixelData);
  355. if (createdAsset)
  356. {
  357. // Set the active image to the created one.
  358. m_component.SetMacroColorAsset(createdAsset.value());
  359. OnCompositionChanged();
  360. }
  361. return createdAsset.has_value();
  362. }
  363. AZStd::vector<uint8_t> EditorTerrainMacroMaterialComponent::ConvertLinearToSrgbGamma(AZStd::span<const uint32_t> pixelBuffer) const
  364. {
  365. AZStd::array<uint8_t, 256> linearToSrgbGamma;
  366. const float uint8Max = static_cast<float>(AZStd::numeric_limits<uint8_t>::max());
  367. // Build up a color conversion array.
  368. for (size_t i = 0; i < linearToSrgbGamma.array_size; ++i)
  369. {
  370. float linearValue = i / uint8Max;
  371. linearToSrgbGamma[i] = aznumeric_cast<uint8_t>(AZ::Color::ConvertSrgbLinearToGamma(linearValue) * uint8Max);
  372. }
  373. AZStd::vector<uint8_t> convertedPixels;
  374. convertedPixels.reserve(pixelBuffer.size() * sizeof(uint32_t));
  375. union ColorBytes
  376. {
  377. struct
  378. {
  379. uint8_t m_red;
  380. uint8_t m_green;
  381. uint8_t m_blue;
  382. uint8_t m_alpha;
  383. };
  384. uint32_t m_rgba;
  385. };
  386. // The pixel buffer consists of R8G8B8A8 values. We'll take each byte individually and convert it from linear to gamma,
  387. // with the exception of the alpha byte, which should remain as-is.
  388. for (auto pixel : pixelBuffer)
  389. {
  390. const ColorBytes *pixelBytes = reinterpret_cast<const ColorBytes*>(&pixel);
  391. convertedPixels.push_back(linearToSrgbGamma[pixelBytes->m_red]);
  392. convertedPixels.push_back(linearToSrgbGamma[pixelBytes->m_green]);
  393. convertedPixels.push_back(linearToSrgbGamma[pixelBytes->m_blue]);
  394. convertedPixels.push_back(pixelBytes->m_alpha);
  395. }
  396. return convertedPixels;
  397. }
  398. } // namespace Terrain