TerrainMacroMaterialTests.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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/Component/ComponentApplication.h>
  9. #include <AzCore/Component/TransformBus.h>
  10. #include <AzFramework/PaintBrush/PaintBrush.h>
  11. #include <Atom/RPI.Reflect/Image/ImageMipChainAssetCreator.h>
  12. #include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
  13. #include <AzFramework/Terrain/TerrainDataRequestBus.h>
  14. #include <GradientSignalTestHelpers.h>
  15. #include <TerrainRenderer/Components/TerrainMacroMaterialComponent.h>
  16. #include <LmbrCentral/Shape/BoxShapeComponentBus.h>
  17. #include <AzTest/AzTest.h>
  18. #include <AZTestShared/Math/MathTestHelpers.h>
  19. #include <Terrain/MockTerrain.h>
  20. #include <Terrain/MockTerrainMacroMaterialBus.h>
  21. #include <TerrainTestFixtures.h>
  22. using ::testing::NiceMock;
  23. using ::testing::AtLeast;
  24. using ::testing::_;
  25. namespace UnitTest
  26. {
  27. class TerrainMacroMaterialComponentTest : public UnitTest::TerrainSystemTestFixture
  28. {
  29. public:
  30. AZ::Data::Asset<AZ::RPI::StreamingImageAsset> CreateMacroColorAsset(
  31. AZ::u32 width, AZ::u32 height, AZStd::span<const uint32_t> data)
  32. {
  33. AZStd::span<const uint8_t> rawPixels(reinterpret_cast<const uint8_t*>(data.data()), data.size() * sizeof(uint32_t));
  34. return CreateImageAssetFromPixelData(width, height, AZ::RHI::Format::R8G8B8A8_UNORM, rawPixels);
  35. }
  36. AZStd::unique_ptr<AZ::Entity> CreateTestMacroMaterialEntity(
  37. float bounds, AZ::Data::Asset<AZ::RPI::StreamingImageAsset> macroColorAsset)
  38. {
  39. auto entity = CreateTestBoxEntity(bounds / 2.0f);
  40. Terrain::TerrainMacroMaterialConfig config;
  41. config.m_macroColorAsset = macroColorAsset;
  42. m_macroMaterialComponent = entity->CreateComponent<Terrain::TerrainMacroMaterialComponent>(config);
  43. ActivateEntity(entity.get());
  44. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Active);
  45. return entity;
  46. }
  47. // Keep track of the Macro Material component so that we have an easy way to access the component ID.
  48. Terrain::TerrainMacroMaterialComponent* m_macroMaterialComponent = nullptr;
  49. };
  50. TEST_F(TerrainMacroMaterialComponentTest, MissingRequiredComponentsActivateFailure)
  51. {
  52. auto entity = CreateEntity();
  53. entity->CreateComponent<Terrain::TerrainMacroMaterialComponent>();
  54. const AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails();
  55. EXPECT_FALSE(sortOutcome.IsSuccess());
  56. entity.reset();
  57. }
  58. TEST_F(TerrainMacroMaterialComponentTest, RequiredComponentsPresentEntityActivateSuccess)
  59. {
  60. // No macro material asset is getting attached, so we shouldn't get any macro material create/destroy notifications.
  61. NiceMock<UnitTest::MockTerrainMacroMaterialNotificationBus> mockMacroMaterialNotifications;
  62. EXPECT_CALL(mockMacroMaterialNotifications, OnTerrainMacroMaterialCreated).Times(0);
  63. EXPECT_CALL(mockMacroMaterialNotifications, OnTerrainMacroMaterialDestroyed).Times(0);
  64. constexpr float BoxHalfBounds = 128.0f;
  65. auto entity = CreateTestBoxEntity(BoxHalfBounds);
  66. entity->CreateComponent<Terrain::TerrainMacroMaterialComponent>();
  67. ActivateEntity(entity.get());
  68. EXPECT_EQ(entity->GetState(), AZ::Entity::State::Active);
  69. entity.reset();
  70. }
  71. TEST_F(TerrainMacroMaterialComponentTest, ComponentWithMacroColorAssetNotifiesMacroMaterialCreationAndDestruction)
  72. {
  73. // We're attaching a loaded macro color asset, so should get macro material create/destroy notifications.
  74. NiceMock<UnitTest::MockTerrainMacroMaterialNotificationBus> mockMacroMaterialNotifications;
  75. EXPECT_CALL(mockMacroMaterialNotifications, OnTerrainMacroMaterialCreated).Times(1);
  76. EXPECT_CALL(mockMacroMaterialNotifications, OnTerrainMacroMaterialDestroyed).Times(1);
  77. // Create a dummy image asset to use for the macro color.
  78. constexpr uint32_t width = 4;
  79. constexpr uint32_t height = 4;
  80. AZStd::vector<uint32_t> pixels(width * height);
  81. auto macroColorAsset = CreateMacroColorAsset(width, height, pixels);
  82. // Create and activate the test entity.
  83. constexpr float BoxBounds = 256.0f;
  84. auto entity = CreateTestMacroMaterialEntity(BoxBounds, macroColorAsset);
  85. // Decativate the entity, which should generate the macro material destroy notification.
  86. entity.reset();
  87. }
  88. TEST_F(TerrainMacroMaterialComponentTest, ComponentWithMacroColorHasWorkingEyedropper)
  89. {
  90. // We're attaching a loaded macro color asset, so should get macro material create/destroy notifications.
  91. NiceMock<UnitTest::MockTerrainMacroMaterialNotificationBus> mockMacroMaterialNotifications;
  92. EXPECT_CALL(mockMacroMaterialNotifications, OnTerrainMacroMaterialCreated).Times(1);
  93. EXPECT_CALL(mockMacroMaterialNotifications, OnTerrainMacroMaterialDestroyed).Times(1);
  94. // Create a Terrain Macro Material in a box that goes from (0, 0, 0) to (4, 4, 4) in world space.
  95. // We'll create a 4x4 image to map onto it, so each pixel is 1 x 1 m in size.
  96. // The lower left corner of the image maps to (0, 0) and the upper right to (4, 4).
  97. uint32_t width = 4;
  98. uint32_t height = 4;
  99. // The pixel values themselves are arbitrary, they're just all set to different values to help verify that the correct pixel
  100. // colors are getting read by the eyedropper at each world location.
  101. AZStd::vector<uint32_t> pixels = {
  102. // 0 - 1 m 1 - 2 m 2 - 3 m 3 - 4 m
  103. 0xF0000000, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, // 3 - 4 m
  104. 0xC0000000, 0xFFC00000, 0xFF00C000, 0xFF0000C0, // 2 - 3 m
  105. 0x80000000, 0xFF800000, 0xFF008000, 0xFF000080, // 1 - 2 m
  106. 0x40000000, 0xFF400000, 0xFF004000, 0xFF000040, // 0 - 1 m
  107. };
  108. auto macroColorAsset = CreateMacroColorAsset(width, height, pixels);
  109. // Create and activate the test entity.
  110. constexpr float BoxBounds = 4.0f;
  111. auto entity = CreateTestMacroMaterialEntity(BoxBounds, macroColorAsset);
  112. AzFramework::PaintBrushSettings brushSettings;
  113. AzFramework::PaintBrush paintBrush({ entity->GetId(), m_macroMaterialComponent->GetId() });
  114. paintBrush.BeginPaintMode();
  115. AZ::Aabb shapeBounds = AZ::Aabb::CreateNull();
  116. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  117. shapeBounds, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
  118. // Loop through each pixel, use the eyedropper in world space to try to look it up, and verify the colors match.
  119. for (uint32_t pixelIndex = 0; pixelIndex < pixels.size(); pixelIndex++)
  120. {
  121. uint32_t pixelX = pixelIndex % width;
  122. uint32_t pixelY = pixelIndex / width;
  123. auto location = PixelCoordinatesToWorldSpace(pixelX, pixelY, shapeBounds, width, height);
  124. // Use the eyedropper for each world position and verify that it matches the color in the image.
  125. AZ::Color pixelColor = paintBrush.UseEyedropper(location);
  126. uint32_t expectedPixel = pixels[pixelIndex];
  127. AZ::Color expectedColor(
  128. aznumeric_cast<uint8_t>((expectedPixel >> 0) & 0xFF),
  129. aznumeric_cast<uint8_t>((expectedPixel >> 8) & 0xFF),
  130. aznumeric_cast<uint8_t>((expectedPixel >> 16) & 0xFF),
  131. aznumeric_cast<uint8_t>((expectedPixel >> 24) & 0xFF));
  132. EXPECT_THAT(pixelColor, UnitTest::IsClose(expectedColor));
  133. }
  134. paintBrush.EndPaintMode();
  135. entity.reset();
  136. }
  137. TEST_F(TerrainMacroMaterialComponentTest, ComponentWithMacroColorCanBePainted)
  138. {
  139. // We're attaching a loaded macro color asset, so should get macro material create/destroy notifications.
  140. NiceMock<UnitTest::MockTerrainMacroMaterialNotificationBus> mockMacroMaterialNotifications;
  141. EXPECT_CALL(mockMacroMaterialNotifications, OnTerrainMacroMaterialCreated).Times(1);
  142. EXPECT_CALL(mockMacroMaterialNotifications, OnTerrainMacroMaterialDestroyed).Times(1);
  143. // Create a Terrain Macro Material in a box that goes from (0, 0, 0) to (4, 4, 4) in world space.
  144. // We'll create a 4x4 image to map onto it, so each pixel is 1 x 1 m in size.
  145. // The lower left corner of the image maps to (0, 0) and the upper right to (4, 4).
  146. uint32_t width = 4;
  147. uint32_t height = 4;
  148. AZStd::vector<uint32_t> pixels(4 * 4);
  149. auto macroColorAsset = CreateMacroColorAsset(width, height, pixels);
  150. // Create and activate the test entity.
  151. constexpr float BoxBounds = 4.0f;
  152. auto entity = CreateTestMacroMaterialEntity(BoxBounds, macroColorAsset);
  153. AZ::Aabb shapeBounds = AZ::Aabb::CreateNull();
  154. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  155. shapeBounds, entity->GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
  156. // Choose color values that are arbitrary and different except for the alpha, which is set to opaque.
  157. AZ::Color brushColor(
  158. aznumeric_cast<uint8_t>(20), aznumeric_cast<uint8_t>(40), aznumeric_cast<uint8_t>(60), aznumeric_cast<uint8_t>(255));
  159. AzFramework::PaintBrushSettings brushSettings;
  160. brushSettings.SetColor(brushColor);
  161. brushSettings.SetSize(1.0f);
  162. EXPECT_THAT(brushSettings.GetColor(), UnitTest::IsClose(brushColor));
  163. constexpr uint32_t paintedPixelX = 2;
  164. constexpr uint32_t paintedPixelY = 1;
  165. auto paintedPixelLocation = PixelCoordinatesToWorldSpace(paintedPixelX, paintedPixelY, shapeBounds, width, height);
  166. AzFramework::PaintBrush paintBrush({ entity->GetId(), m_macroMaterialComponent->GetId() });
  167. paintBrush.BeginPaintMode();
  168. AZ::Color startColor = paintBrush.UseEyedropper(paintedPixelLocation);
  169. EXPECT_THAT(startColor, UnitTest::IsClose(AZ::Color(0.0f, 0.0f, 0.0f, 0.0f)));
  170. paintBrush.BeginBrushStroke(brushSettings);
  171. paintBrush.PaintToLocation(paintedPixelLocation, brushSettings);
  172. paintBrush.EndBrushStroke();
  173. // Loop through each pixel, use the eyedropper in world space to try to look it up, and verify the colors match expectations.
  174. // Most of the pixels should still be (0, 0, 0, 0), but one pixel should be (0.25, 0.5, 0.75, 0). Note that the alpha remains 0
  175. // even though we're painting with full opacity. This is because the alpha in the original image is preserved through painting
  176. // and isn't modified. The opacity of the brush just affects how the brush merges with the image.
  177. for (uint32_t pixelIndex = 0; pixelIndex < pixels.size(); pixelIndex++)
  178. {
  179. uint32_t pixelX = pixelIndex % width;
  180. uint32_t pixelY = pixelIndex / width;
  181. auto queryLocation = PixelCoordinatesToWorldSpace(pixelX, pixelY, shapeBounds, width, height);
  182. AZ::Color expectedColor = (pixelX == paintedPixelX) && (pixelY == paintedPixelY)
  183. ? AZ::Color(brushColor.GetR(), brushColor.GetG(), brushColor.GetB(), 0.0f)
  184. : AZ::Color(0.0f);
  185. // Use the eyedropper for each world position and verify that it matches the expected color.
  186. AZ::Color pixelColor = paintBrush.UseEyedropper(queryLocation);
  187. EXPECT_THAT(pixelColor, UnitTest::IsCloseTolerance(expectedColor, 0.001f));
  188. }
  189. paintBrush.EndPaintMode();
  190. entity.reset();
  191. }
  192. } // namespace UnitTest