GradientSignalTransformTests.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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 <Tests/GradientSignalTestFixtures.h>
  9. #include <AzTest/AzTest.h>
  10. #include <AzCore/Asset/AssetManager.h>
  11. #include <AzCore/Memory/PoolAllocator.h>
  12. #include <AzCore/Math/Vector2.h>
  13. #include <AZTestShared/Math/MathTestHelpers.h>
  14. #include <GradientSignal/Components/GradientTransformComponent.h>
  15. namespace UnitTest
  16. {
  17. struct GradientSignalTransformTestsFixture : public GradientSignalTest
  18. {
  19. // By default, we'll use a shape half extents of (5, 10, 20) for every test, and a world translation of (100, 200, 300).
  20. struct GradientTransformSetupData
  21. {
  22. GradientSignal::WrappingType m_wrappingType{ GradientSignal::WrappingType::None };
  23. AZ::Vector3 m_shapeHalfExtents{ 5.0f, 10.0f, 20.0f };
  24. AZ::Vector3 m_worldTranslation{ 100.0f, 200.0f, 300.0f };
  25. float m_frequencyZoom{ 1.0f };
  26. };
  27. struct GradientTransformTestData
  28. {
  29. AZ::Vector3 m_positionToTest;
  30. AZ::Vector3 m_expectedOutputUVW;
  31. bool m_expectedOutputRejectionResult;
  32. };
  33. static constexpr float UvEpsilon = GradientSignal::GradientTransform::UvEpsilon;
  34. void TestGradientTransform(const GradientTransformSetupData& setup, const GradientTransformTestData& test)
  35. {
  36. AZ::Aabb shapeBounds = AZ::Aabb::CreateCenterHalfExtents(AZ::Vector3::CreateZero(), setup.m_shapeHalfExtents);
  37. AZ::Matrix3x4 transform = AZ::Matrix3x4::CreateTranslation(setup.m_worldTranslation);
  38. float frequencyZoom = setup.m_frequencyZoom;
  39. GradientSignal::WrappingType wrappingType = setup.m_wrappingType;
  40. AZ::Vector3 outUVW;
  41. bool wasPointRejected;
  42. // Perform the query with a 3D gradient and verify that the results match expectations.
  43. GradientSignal::GradientTransform gradientTransform3d(shapeBounds, transform, true, frequencyZoom, wrappingType);
  44. gradientTransform3d.TransformPositionToUVW(test.m_positionToTest, outUVW, wasPointRejected);
  45. EXPECT_THAT(outUVW, IsClose(test.m_expectedOutputUVW));
  46. EXPECT_EQ(wasPointRejected, test.m_expectedOutputRejectionResult);
  47. // Perform the query with a 2D gradient and verify that the results match, but always returns a W value of 0.
  48. GradientSignal::GradientTransform gradientTransform2d(shapeBounds, transform, false, frequencyZoom, wrappingType);
  49. gradientTransform2d.TransformPositionToUVW(test.m_positionToTest, outUVW, wasPointRejected);
  50. EXPECT_THAT(outUVW, IsClose(AZ::Vector3(test.m_expectedOutputUVW.GetX(), test.m_expectedOutputUVW.GetY(), 0.0f)));
  51. EXPECT_EQ(wasPointRejected, test.m_expectedOutputRejectionResult);
  52. }
  53. };
  54. TEST_F(GradientSignalTransformTestsFixture, UnboundedWrappingReturnsTranslatedInput)
  55. {
  56. GradientTransformSetupData setup = { GradientSignal::WrappingType::None };
  57. GradientTransformTestData test = {
  58. // Input position to query
  59. { 0.0f, 0.0f, 0.0f },
  60. // Output: For no wrapping, the output is just the input position offset by the world translation.
  61. { -100.0f, -200.0f, -300.0f }, false
  62. };
  63. TestGradientTransform(setup, test);
  64. }
  65. TEST_F(GradientSignalTransformTestsFixture, ClampToEdgeReturnsValuesClampedToShapeBounds)
  66. {
  67. GradientTransformSetupData setup = { GradientSignal::WrappingType::ClampToEdge };
  68. GradientTransformTestData tests[] = {
  69. // Test: Input point far below minimum shape bounds
  70. // Our input point is below the minimum of shape bounds, so the result should be the minimum corner of the shape.
  71. { { 0.0f, 0.0f, 0.0f }, { -5.0f, -10.0f, -20.0f }, false },
  72. // Test: Input point directly on minimum shape bounds
  73. // Our input point is directly on the minimum of shape bounds, so the result should be the minimum corner of the shape.
  74. { { 95.0f, 190.0f, 280.0f }, { -5.0f, -10.0f, -20.0f }, false },
  75. // Test: Input point inside shape bounds
  76. // Our input point is inside the shape bounds, so the result is just input - translation.
  77. { { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
  78. // Test: Input point directly on maximum shape bounds
  79. // On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
  80. // expected results are the max shape corner - epsilon.
  81. { { 105.0f, 210.0f, 320.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false },
  82. // Test: Input point far above maximum shape bounds
  83. // On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
  84. // expected results are the max shape corner - epsilon.
  85. { { 1000.0f, 1000.0f, 1000.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false },
  86. };
  87. for (auto& test : tests)
  88. {
  89. TestGradientTransform(setup, test);
  90. }
  91. }
  92. TEST_F(GradientSignalTransformTestsFixture, MirrorReturnsValuesMirroredBasedOnShapeBounds)
  93. {
  94. /* Here's how the results are expected to work for various inputs when using Mirror wrapping.
  95. * This assumes shape half extents of (5, 10, 20), and a center translation of (100, 200, 300):
  96. * Inputs: Outputs:
  97. * ... ...
  98. * (75, 150, 200) - (85, 170, 240) (-5, -10, -20) to (5, 10, 20) // forward mirror
  99. * (85, 170, 240) - (95, 190, 280) (5, 10, 20) to (-5, -10, -20) // back mirror
  100. * (95, 190, 280) - (105, 210, 320) (-5, -10, -20) to (5, 10, 20) // starting point
  101. * (105, 210, 320) - (115, 230, 360) (5, 10, 20) to (-5, -10, -20) // back mirror
  102. * (115, 230, 360) - (125, 250, 400) (-5, -10, -20) to (5, 10, 20) // forward mirror
  103. * ... ...
  104. * When below the starting point, both forward and back mirrors will be adjusted by UvEpsilon except for points that fall on the
  105. * shape minimums.
  106. * When above the starting point, only back mirrors will be adjusted by UvEpsilon.
  107. */
  108. GradientTransformSetupData setup = { GradientSignal::WrappingType::Mirror };
  109. GradientTransformTestData tests[] = {
  110. // Test: Input exactly 2x below minimum bounds
  111. // When landing exactly on the 2x boundary, we return the minumum shape bounds. There is no adjustment by epsilon
  112. // on the minimum side of the bounds, even when we're in a mirror below the shape bounds.
  113. { { 75.0f, 150.0f, 200.0f }, { -5.0f, -10.0f, -20.0f }, false },
  114. // Test: Input within 2nd mirror repeat below minimum bounds
  115. // The second mirror repeat should go forward in values, but will be adjusted by UvEpsilon since we're below the
  116. // minimum bounds.
  117. { { 84.0f, 168.0f, 237.0f }, { 4.0f - UvEpsilon, 8.0f - UvEpsilon, 17.0f - UvEpsilon }, false },
  118. // Test: Input exactly 1x below minimum bounds.
  119. // When landing exactly on the 1x boundary, we return the maximum shape bounds minus epsilon.
  120. { { 85.0f, 170.0f, 240.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false },
  121. // Test: Input within 1st mirror repeat below minimum bounds
  122. // The first mirror repeat should go backwards in values, but will be adjusted by UvEpsilon since we're below the
  123. // minimum bounds.
  124. { { 94.0f, 188.0f, 277.0f }, { -4.0f - UvEpsilon, -8.0f - UvEpsilon, -17.0f - UvEpsilon }, false },
  125. // Test: Input inside shape bounds
  126. // The translated input position is (1, 2, 3) is inside the shape bounds, so we should just get the translated
  127. // position back as output.
  128. { { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
  129. // Test: Input within 1st mirror repeat above maximum bounds
  130. // The first mirror repeat should go backwards in values. We're above the maximum bounds, so the expected result
  131. // is (4, 8, 17) minus an epsilon.
  132. { { 106.0f, 212.0f, 323.0f }, { 4.0f - UvEpsilon, 8.0f - UvEpsilon, 17.0f - UvEpsilon }, false },
  133. // Test: Input exactly 2x above minimum bounds.
  134. // When landing exactly on the 2x boundary, we return the exact minimum value again.
  135. { { 115.0f, 230.0f, 360.0f }, { -5.0f, -10.0f, -20.0f }, false },
  136. // Test: Input within 2nd mirror repeat above maximum bounds
  137. // The second mirror repeat should go forwards in values. We're above the maximum bounds, so the expected result
  138. // is (-4, -8, -17) with no epsilon.
  139. { { 116.0f, 232.0f, 363.0f }, { -4.0f, -8.0f, -17.0f }, false },
  140. // Test: Input exactly 2x above maximum bounds
  141. // When landing exactly on the 2x boundary, we return the maximum adjusted by the epsilon again.
  142. { { 125.0f, 250.0f, 400.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false }
  143. };
  144. for (auto& test : tests)
  145. {
  146. TestGradientTransform(setup, test);
  147. }
  148. }
  149. TEST_F(GradientSignalTransformTestsFixture, RepeatReturnsRepeatingValuesBasedOnShapeBounds)
  150. {
  151. /* Here's how the results are expected to work for various inputs when using Repeat wrapping.
  152. * This assumes shape half extents of (5, 10, 20), and a center translation of (100, 200, 300):
  153. * Inputs: Outputs:
  154. * ... ...
  155. * (75, 150, 200) - (85, 170, 240) (-5, -10, -20) to (5, 10, 20)
  156. * (85, 170, 240) - (95, 190, 280) (-5, -10, -20) to (5, 10, 20)
  157. * (95, 190, 280) - (105, 210, 320) (-5, -10, -20) to (5, 10, 20) // starting point
  158. * (105, 210, 320) - (115, 230, 360) (-5, -10, -20) to (5, 10, 20)
  159. * (115, 230, 360) - (125, 250, 400) (-5, -10, -20) to (5, 10, 20)
  160. * ... ...
  161. * Every shape min/max boundary point below the starting point will have the max shape value.
  162. * Every shape min/max boundary point above the starting point with have the min shape value.
  163. */
  164. GradientTransformSetupData setup = { GradientSignal::WrappingType::Repeat };
  165. GradientTransformTestData tests[] = {
  166. // Test: 2x below minimum shape bounds
  167. // We're on a shape boundary below the minimum bounds, so it should return the maximum.
  168. { { 75.0f, 150.0f, 200.0f }, { 5.0f, 10.0f, 20.0f }, false },
  169. // Test: Input within 2nd repeat below minimum shape bounds
  170. // Every repeat should go forwards in values.
  171. { { 76.0f, 152.0f, 203.0f }, { -4.0f, -8.0f, -17.0f }, false },
  172. // Test: 1x below minimum shape bounds
  173. // We're on a shape boundary below the minimum bounds, so it should return the maximum.
  174. { { 85.0f, 170.0f, 240.0f }, { 5.0f, 10.0f, 20.0f }, false },
  175. // Test: Input within 1st repeat below minimum shape bounds
  176. // Every repeat should go forwards in values.
  177. { { 86.0f, 172.0f, 243.0f }, { -4.0f, -8.0f, -17.0f }, false },
  178. // Test: Input exactly on minimum shape bounds
  179. // This should return the actual minimum bounds.
  180. { { 95.0f, 190.0f, 280.0f }, { -5.0f, -10.0f, -20.0f }, false },
  181. // Test: Input inside shape bounds
  182. // This should return the mapped value.
  183. { { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
  184. // Test: Input exactly on maximum shape bounds
  185. // We're on a shape boundary above the minimum bounds, so it should return the minimum.
  186. { { 105.0f, 210.0f, 320.0f }, { -5.0f, -10.0f, -20.0f }, false },
  187. // Test: Input within 1st repeat above maximum shape bounds
  188. // Every repeat should go forwards in values.
  189. { { 106.0f, 212.0f, 323.0f }, { -4.0f, -8.0f, -17.0f }, false },
  190. // Test: 1x above maximum shape bounds
  191. // We're on a shape boundary above the minimum bounds, so it should return the minimum.
  192. { { 105.0f, 210.0f, 320.0f }, { -5.0f, -10.0f, -20.0f }, false },
  193. // Test: Input within 2nd repeat above maximum shape bounds
  194. // Every repeat should go forwards in values.
  195. { { 106.0f, 212.0f, 323.0f }, { -4.0f, -8.0f, -17.0f }, false },
  196. };
  197. for (auto& test : tests)
  198. {
  199. TestGradientTransform(setup, test);
  200. }
  201. }
  202. TEST_F(GradientSignalTransformTestsFixture, ClampToZeroReturnsClampedValuesBasedOnShapeBounds)
  203. {
  204. GradientTransformSetupData setup = { GradientSignal::WrappingType::ClampToZero };
  205. GradientTransformTestData tests[] = {
  206. // Test: Input point far below minimum shape bounds
  207. // Our input point is below the minimum of shape bounds, so the result should be the minimum corner of the shape.
  208. // Points outside the shape bounds should return "true" for rejected.
  209. { { 0.0f, 0.0f, 0.0f }, { -5.0f, -10.0f, -20.0f }, true },
  210. // Test: Input point directly on minimum shape bounds
  211. // Our input point is directly on the minimum of shape bounds, so the result should be the minimum corner of the shape.
  212. { { 95.0f, 190.0f, 280.0f }, { -5.0f, -10.0f, -20.0f }, false },
  213. // Test: Input point inside shape bounds
  214. // Our input point is inside the shape bounds, so the result is just input - translation.
  215. { { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
  216. // Test: Input point directly on maximum shape bounds
  217. // On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
  218. // expected results are the max shape corner - epsilon.
  219. // Points outside the shape bounds (which includes the maximum edge of the shape bounds) should return "true" for rejected.
  220. { { 105.0f, 210.0f, 320.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, true },
  221. // Test: Input point far above maximum shape bounds
  222. // On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
  223. // expected results are the max shape corner - epsilon.
  224. // Points outside the shape bounds should return "true" for rejected.
  225. { { 1000.0f, 1000.0f, 1000.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, true },
  226. };
  227. for (auto& test : tests)
  228. {
  229. TestGradientTransform(setup, test);
  230. }
  231. }
  232. }