MacroMaterialImageModification.h 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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. #pragma once
  9. #include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
  10. #include <AzCore/Asset/AssetCommon.h>
  11. #include <AzCore/Component/Component.h>
  12. #include <AzCore/Math/Vector2.h>
  13. #include <AzCore/Serialization/Json/BaseJsonSerializer.h>
  14. #include <AzCore/Serialization/Json/RegistrationContext.h>
  15. #include <AzCore/std/parallel/shared_mutex.h>
  16. #include <AzFramework/PaintBrush/PaintBrushNotificationBus.h>
  17. #include <TerrainRenderer/TerrainMacroMaterialBus.h>
  18. namespace Terrain
  19. {
  20. //! Tracks all of the image modifications for a single continuous paint stroke. Since most modifications will only affect a small
  21. //! portion of an image, this buffer divides the total image space into fixed-size tiles and only creates an individual tile
  22. //! buffer when at least one pixel in that tile's space is modified.
  23. //! While painting the paint stroke, this buffer caches all of the unmodified texture values and the modifications for each
  24. //! modified pixel. The buffer is used to create a special "stroke layer" that accumulates opacity for each stroke, which
  25. //! then combines with the stroke opacity, stroke color, and blend mode to blend back into the base layer.
  26. //! After the paint stroke finishes, the stroke buffer ownership is handed over to the undo/redo system so that it can
  27. //! be used to undo/redo each individual paint stroke.
  28. class ImageTileBuffer
  29. {
  30. public:
  31. ImageTileBuffer(uint32_t imageWidth, uint32_t imageHeight, AZ::EntityId imageGradientEntityId);
  32. ~ImageTileBuffer() = default;
  33. //! Returns true if we don't have any pixel modifications, false if we do.
  34. bool Empty() const;
  35. //! Get the original color value for the given pixel index.
  36. //! Since we "lazy-cache" our unmodified image as tiles, create it here the first time we request a pixel from a tile.
  37. AZStd::pair<AZ::Color, float> GetOriginalPixelValueAndOpacity(const PixelIndex& pixelIndex);
  38. //! Set a modified color value for the given pixel index.
  39. void SetModifiedPixelValue(const PixelIndex& pixelIndex, AZ::Color modifiedValue, float opacity);
  40. //! For undo/redo operations, apply the buffer of changes back to the terrain macro texture.
  41. void ApplyChangeBuffer(bool undo);
  42. private:
  43. //! Given a pixel index, get the tile index that it maps to.
  44. uint32_t GetTileIndex(const PixelIndex& pixelIndex) const;
  45. //! Given a tile index, get the absolute start pixel index for the upper left corner of the tile.
  46. PixelIndex GetStartPixelIndex(uint32_t tileIndex) const;
  47. // Given a pixel index, get the relative pixel index within the tile
  48. uint32_t GetPixelTileIndex(const PixelIndex& pixelIndex) const;
  49. //! Create an image tile initialized with the macro texture values if the tile doesn't already exist.
  50. void CreateImageTile(uint32_t tileIndex);
  51. //! Size of each modified image tile that we'll cache off.
  52. //! This size is chosen somewhat arbitrarily to keep the number of tiles balanced at a reasonable size.
  53. //! It should also ideally be a power of 2 for faster division and mods.
  54. static inline constexpr uint32_t ImageTileSize = 32;
  55. //! Keeps track of all the unmodified and modified pixel values, as well as our paint stroke opacity layer, for an NxN tile.
  56. //! We store it a struct of arrays instead of an array of structs for better compatibility with bulk APIs,
  57. //! where we can just pass in a full array of values to update a full tile's worth of values at once.
  58. struct ImageTile
  59. {
  60. AZStd::array<AZ::Color, ImageTileSize * ImageTileSize> m_unmodifiedData;
  61. AZStd::array<AZ::Color, ImageTileSize * ImageTileSize> m_modifiedData;
  62. AZStd::array<float, ImageTileSize * ImageTileSize> m_modifiedDataOpacity;
  63. };
  64. //! A vector of pointers to image tiles.
  65. //! All of the tile pointer entries are always expected to exist, even if the pointers themselves are null.
  66. using ImageTileList = AZStd::vector<AZStd::unique_ptr<ImageTile>>;
  67. //! The actual storage for the set of image tile pointers. Image tiles get created on-demand whenever pixels in them change.
  68. //! This ultimately contains all of the changes for one continuous brush stroke.
  69. ImageTileList m_paintedImageTiles;
  70. //! The number of tiles we're creating in the X and Y directions to contain a full texture.
  71. const uint32_t m_numTilesX = 0;
  72. const uint32_t m_numTilesY = 0;
  73. //! The entity ID of the texture that we're modifying.
  74. const AZ::EntityId m_modifiedEntityId;
  75. //! Track whether or not we've modified any pixels.
  76. bool m_modifiedAnyPixels = false;
  77. };
  78. //! Tracks all of the data related to the macro material image size.
  79. struct MacroMaterialImageSizeData
  80. {
  81. //! The meters per pixel in each direction for this macro material.
  82. //! These help us query the paintbrush for exactly one world position per image pixel.
  83. float m_metersPerPixelX = 0.0f;
  84. float m_metersPerPixelY = 0.0f;
  85. //! The world bounds of the macro material
  86. AZ::Aabb m_macroMaterialBounds;
  87. //! Image width and height in pixels.
  88. int16_t m_imageWidth = 0;
  89. int16_t m_imageHeight = 0;
  90. };
  91. //! Handles all of the calculations for figuring out the dirty region AABB for the macro material texture.
  92. //! It tracks the dirty pixel region and converts that back to world space bounds on request.
  93. //! This ensures that our bounds fully encompass the world space pixel volumes, and not just their corners or centers.
  94. struct ModifiedImageRegion
  95. {
  96. public:
  97. ModifiedImageRegion() = default;
  98. ModifiedImageRegion(const MacroMaterialImageSizeData& imageData);
  99. //! Add a pixel's pixel index into the dirty region.
  100. void AddPoint(const PixelIndex& pixelIndex);
  101. //! Calculate the dirty region in world space that reflects everywhere that's changed.
  102. AZ::Aabb GetDirtyRegion();
  103. //! Returns true if there is a dirty region, false if there isn't.
  104. bool IsModified() const
  105. {
  106. return m_isModified;
  107. }
  108. private:
  109. // Adds the full bounds of a pixel in local pixel indices to the given AABB.
  110. static void AddPixelAabb(const MacroMaterialImageSizeData& imageData, int16_t pixelX, int16_t pixelY, AZ::Aabb& region);
  111. MacroMaterialImageSizeData m_imageData;
  112. PixelIndex m_minModifiedPixelIndex;
  113. PixelIndex m_maxModifiedPixelIndex;
  114. bool m_isModified = false;
  115. };
  116. //! Top-level class that handles all of the actual image modification calculations for a paintbrush.
  117. class MacroMaterialImageModifier : private AzFramework::PaintBrushNotificationBus::Handler
  118. {
  119. public:
  120. MacroMaterialImageModifier(const AZ::EntityComponentIdPair& entityComponentIdPair);
  121. ~MacroMaterialImageModifier() override;
  122. protected:
  123. // PaintBrushNotificationBus overrides
  124. void OnBrushStrokeBegin(const AZ::Color& color) override;
  125. void OnBrushStrokeEnd() override;
  126. void OnPaint(const AZ::Color& color, const AZ::Aabb& dirtyArea, ValueLookupFn& valueLookupFn, BlendFn& blendFn) override;
  127. void OnSmooth(
  128. const AZ::Color& color,
  129. const AZ::Aabb& dirtyArea,
  130. ValueLookupFn& valueLookupFn,
  131. AZStd::span<const AZ::Vector3> valuePointOffsets,
  132. SmoothFn& smoothFn) override;
  133. AZ::Color OnGetColor(const AZ::Vector3& brushCenter) const override;
  134. void OnPaintSmoothInternal(
  135. const AZ::Aabb& dirtyArea,
  136. ValueLookupFn& valueLookupFn,
  137. AZStd::function<AZ::Color(const AZ::Vector3& worldPosition, AZ::Color gradientValue, float opacity)> combineFn);
  138. private:
  139. //! Keeps a local copy of all the image size data that's needed for locating pixels and calculating dirty regions.
  140. MacroMaterialImageSizeData m_imageData;
  141. //! A buffer to accumulate a single paint stroke into. This buffer is used to ensure that within a single paint stroke,
  142. //! we only perform an operation on a pixel once, not multiple times.
  143. //! After the paint stroke is complete, this buffer is handed off to the undo/redo batch so that we can undo/redo each stroke.
  144. AZStd::shared_ptr<ImageTileBuffer> m_strokeBuffer;
  145. //! Track the dirty region for each brush stroke so that we can store it in the undo/redo buffer
  146. //! to send with change notifications.
  147. ModifiedImageRegion m_modifiedStrokeRegion;
  148. //! The entity/component that owns this paintbrush.
  149. AZ::EntityComponentIdPair m_ownerEntityComponentId;
  150. };
  151. } // namespace Terrain