GradientSignalSurfaceTests.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  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 <AzTest/AzTest.h>
  9. #include <AzCore/std/smart_ptr/make_shared.h>
  10. #include <AzCore/Math/MathUtils.h>
  11. #include <Tests/GradientSignalTestFixtures.h>
  12. #include <GradientSignal/Components/ConstantGradientComponent.h>
  13. #include <GradientSignal/Components/GradientSurfaceDataComponent.h>
  14. namespace UnitTest
  15. {
  16. struct GradientSignalSurfaceTestsFixture
  17. : public GradientSignalTest
  18. {
  19. void SetSurfacePoint(AzFramework::SurfaceData::SurfacePoint& point, AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector<AZStd::pair<AZStd::string, float>> tags)
  20. {
  21. point.m_position = position;
  22. point.m_normal = normal;
  23. for (auto& tag : tags)
  24. {
  25. point.m_surfaceTags.emplace_back(SurfaceData::SurfaceTag(tag.first), tag.second);
  26. }
  27. }
  28. bool SurfacePointsAreEqual(const AzFramework::SurfaceData::SurfacePoint& lhs, const AzFramework::SurfaceData::SurfacePoint& rhs)
  29. {
  30. if ((lhs.m_position != rhs.m_position) || (lhs.m_normal != rhs.m_normal)
  31. || (lhs.m_surfaceTags.size() != rhs.m_surfaceTags.size()))
  32. {
  33. return false;
  34. }
  35. for (auto& mask : lhs.m_surfaceTags)
  36. {
  37. auto maskEntry = AZStd::find_if(
  38. rhs.m_surfaceTags.begin(), rhs.m_surfaceTags.end(),
  39. [mask](const AzFramework::SurfaceData::SurfaceTagWeight& weight) -> bool
  40. {
  41. return (mask.m_surfaceType == weight.m_surfaceType) && (mask.m_weight == weight.m_weight);
  42. });
  43. if (maskEntry == rhs.m_surfaceTags.end())
  44. {
  45. return false;
  46. }
  47. }
  48. return true;
  49. }
  50. bool SurfacePointsAreEqual(
  51. const AZ::Vector3& lhsPosition, const AZ::Vector3& lhsNormal, const SurfaceData::SurfaceTagWeights& lhsWeights,
  52. const AzFramework::SurfaceData::SurfacePoint& rhs)
  53. {
  54. return ((lhsPosition == rhs.m_position)
  55. && (lhsNormal == rhs.m_normal)
  56. && (lhsWeights.SurfaceWeightsAreEqual(rhs.m_surfaceTags)));
  57. }
  58. void TestGradientSurfaceDataComponent(float gradientValue, float thresholdMin, float thresholdMax, AZStd::vector<AZStd::string> tags, bool usesShape,
  59. const AzFramework::SurfaceData::SurfacePoint& input,
  60. const AzFramework::SurfaceData::SurfacePoint& expectedOutput)
  61. {
  62. // Create a mock shape entity in case our gradient test uses shape constraints.
  63. // The mock shape is a cube that goes from -0.5 to 0.5 in space.
  64. auto mockShapeEntity = CreateTestEntity(0.5f);
  65. ActivateEntity(mockShapeEntity.get());
  66. // For ease of testing, use a constant gradient as our input gradient.
  67. GradientSignal::ConstantGradientConfig constantGradientConfig;
  68. constantGradientConfig.m_value = gradientValue;
  69. // Create the test configuration for the GradientSignalSurfaceData component
  70. GradientSignal::GradientSurfaceDataConfig config;
  71. config.m_thresholdMin = thresholdMin;
  72. config.m_thresholdMax = thresholdMax;
  73. for (auto& tag : tags)
  74. {
  75. config.AddTag(tag);
  76. }
  77. // Either point to our shape entity or set it to an invalid ID if we don't want to use a shape constraint for this test.
  78. if (usesShape)
  79. {
  80. config.m_shapeConstraintEntityId = mockShapeEntity->GetId();
  81. }
  82. else
  83. {
  84. config.m_shapeConstraintEntityId = AZ::EntityId();
  85. }
  86. // Create the test entity with the GradientSurfaceData component and the required gradient dependency
  87. auto entity = CreateEntity();
  88. entity->CreateComponent<GradientSignal::ConstantGradientComponent>(constantGradientConfig);
  89. entity->CreateComponent<GradientSignal::GradientSurfaceDataComponent>(config);
  90. ActivateEntity(entity.get());
  91. // Get our registered modifier handle (and verify that it's valid)
  92. SurfaceData::SurfaceDataRegistryHandle modifierHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
  93. modifierHandle = AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfaceDataModifierHandle(entity->GetId());
  94. EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle);
  95. // Call ModifySurfacePoints and verify the results
  96. SurfaceData::SurfacePointList pointList;
  97. pointList.StartListConstruction(AZStd::span<const AzFramework::SurfaceData::SurfacePoint>(&input, 1));
  98. pointList.ModifySurfaceWeights(modifierHandle);
  99. pointList.EndListConstruction();
  100. ASSERT_EQ(pointList.GetSize(), 1);
  101. pointList.EnumeratePoints([this, expectedOutput](
  102. [[maybe_unused]] size_t inPositionIndex, const AZ::Vector3& position, const AZ::Vector3& normal,
  103. const SurfaceData::SurfaceTagWeights& masks)
  104. {
  105. EXPECT_TRUE(SurfacePointsAreEqual(position, normal, masks, expectedOutput));
  106. return true;
  107. });
  108. }
  109. };
  110. TEST_F(GradientSignalSurfaceTestsFixture, GradientSignalSurfaceComponent_PointInThreshold)
  111. {
  112. // Verify that for a gradient value within the threshold, the output point contains the
  113. // correct tag and gradient value.
  114. AzFramework::SurfaceData::SurfacePoint input;
  115. AzFramework::SurfaceData::SurfacePoint expectedOutput;
  116. const char* tag = "test_mask";
  117. // Select a gradient value within the threshold range below
  118. float gradientValue = 0.5f;
  119. // Set arbitrary input data
  120. SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
  121. // Output should match the input, but with an added tag / value
  122. SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, { AZStd::pair<AZStd::string, float>(tag, gradientValue) });
  123. TestGradientSurfaceDataComponent(
  124. gradientValue, // constant gradient value
  125. 0.1f, // min threshold
  126. 1.0f, // max threshold
  127. { tag }, // supported tags
  128. false, // uses surface bounds?
  129. input,
  130. expectedOutput);
  131. }
  132. TEST_F(GradientSignalSurfaceTestsFixture, GradientSignalSurfaceComponent_PointOutsideThreshold)
  133. {
  134. // Verify that for a gradient value outside the threshold, the output point contains no tags / values.
  135. AzFramework::SurfaceData::SurfacePoint input;
  136. AzFramework::SurfaceData::SurfacePoint expectedOutput;
  137. const char* tag = "test_mask";
  138. // Choose a value outside the threshold range
  139. float gradientValue = 0.05f;
  140. // Set arbitrary input data
  141. SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
  142. // Output should match the input - no extra tags / values should be added.
  143. SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, {});
  144. TestGradientSurfaceDataComponent(
  145. gradientValue, // constant gradient value
  146. 0.1f, // min threshold
  147. 1.0f, // max threshold
  148. { tag }, // supported tags
  149. false, // uses surface bounds?
  150. input,
  151. expectedOutput);
  152. }
  153. TEST_F(GradientSignalSurfaceTestsFixture, GradientSignalSurfaceComponent_PointInThresholdMultipleTags)
  154. {
  155. // Verify that if the component has multiple tags, all of them get put on the output with the same gradient value.
  156. AzFramework::SurfaceData::SurfacePoint input;
  157. AzFramework::SurfaceData::SurfacePoint expectedOutput;
  158. const char* tag1 = "test_mask1";
  159. const char* tag2 = "test_mask2";
  160. // Select a gradient value within the threshold range below
  161. float gradientValue = 0.5f;
  162. // Set arbitrary input data
  163. SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
  164. // Output should match the input, but with two added tags
  165. SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
  166. { AZStd::pair<AZStd::string, float>(tag1, gradientValue), AZStd::pair<AZStd::string, float>(tag2, gradientValue) });
  167. TestGradientSurfaceDataComponent(
  168. gradientValue, // constant gradient value
  169. 0.1f, // min threshold
  170. 1.0f, // max threshold
  171. { tag1, tag2 }, // supported tags
  172. false, // uses surface bounds?
  173. input,
  174. expectedOutput);
  175. }
  176. TEST_F(GradientSignalSurfaceTestsFixture, GradientSignalSurfaceComponent_PreservesInputTags)
  177. {
  178. // Verify that the output contains input tags that are NOT on the modification list and adds any
  179. // new tags that weren't in the input
  180. AzFramework::SurfaceData::SurfacePoint input;
  181. AzFramework::SurfaceData::SurfacePoint expectedOutput;
  182. const char* preservedTag = "preserved_tag";
  183. const char* modifierTag = "modifier_tag";
  184. // Select a gradient value within the threshold range below
  185. float gradientValue = 0.5f;
  186. // Set arbitrary input data
  187. SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::pair<AZStd::string, float>(preservedTag, 1.0f) });
  188. // Output should match the input, but with two added tags
  189. SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
  190. { AZStd::pair<AZStd::string, float>(preservedTag, 1.0f), AZStd::pair<AZStd::string, float>(modifierTag, gradientValue) });
  191. TestGradientSurfaceDataComponent(
  192. gradientValue, // constant gradient value
  193. 0.1f, // min threshold
  194. 1.0f, // max threshold
  195. { modifierTag }, // supported tags
  196. false, // uses surface bounds?
  197. input,
  198. expectedOutput);
  199. }
  200. TEST_F(GradientSignalSurfaceTestsFixture, GradientSignalSurfaceComponent_KeepsHigherValueFromInput)
  201. {
  202. // Verify that if the input has a higher value on the tag than the modifier, it keeps the higher value.
  203. AzFramework::SurfaceData::SurfacePoint input;
  204. AzFramework::SurfaceData::SurfacePoint expectedOutput;
  205. const char* tag = "test_mask";
  206. // Select a gradient value within the threshold range below
  207. float gradientValue = 0.5f;
  208. // Select an input value that's higher than the gradient value
  209. float inputValue = 0.75f;
  210. // Set arbitrary input data
  211. SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::pair<AZStd::string, float>(tag, inputValue) });
  212. // Output should match the input - the higher input value on the tag is preserved
  213. SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
  214. { AZStd::pair<AZStd::string, float>(tag, inputValue) });
  215. TestGradientSurfaceDataComponent(
  216. gradientValue, // constant gradient value
  217. 0.1f, // min threshold
  218. 1.0f, // max threshold
  219. { tag }, // supported tags
  220. false, // uses surface bounds?
  221. input,
  222. expectedOutput);
  223. }
  224. TEST_F(GradientSignalSurfaceTestsFixture, GradientSignalSurfaceComponent_KeepsHigherValueFromModifier)
  225. {
  226. // Verify that if the input has a lower value on the tag than the modifier, it keeps the higher value.
  227. AzFramework::SurfaceData::SurfacePoint input;
  228. AzFramework::SurfaceData::SurfacePoint expectedOutput;
  229. const char* tag = "test_mask";
  230. // Select a gradient value within the threshold range below
  231. float gradientValue = 0.5f;
  232. // Select an input value that's lower than the gradient value
  233. float inputValue = 0.25f;
  234. // Set arbitrary input data
  235. SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::pair<AZStd::string, float>(tag, inputValue) });
  236. // Output should match the input, except that the value on the tag gets the higher modifier value
  237. SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
  238. { AZStd::pair<AZStd::string, float>(tag, gradientValue) });
  239. TestGradientSurfaceDataComponent(
  240. gradientValue, // constant gradient value
  241. 0.1f, // min threshold
  242. 1.0f, // max threshold
  243. { tag }, // supported tags
  244. false, // uses surface bounds?
  245. input,
  246. expectedOutput);
  247. }
  248. TEST_F(GradientSignalSurfaceTestsFixture, GradientSignalSurfaceComponent_UnboundedRangeWithoutShape)
  249. {
  250. // Verify that if no shape has been added, the component modifies points in unbounded space
  251. AzFramework::SurfaceData::SurfacePoint input;
  252. AzFramework::SurfaceData::SurfacePoint expectedOutput;
  253. const char* tag = "test_mask";
  254. // Select a gradient value within the threshold range below
  255. float gradientValue = 0.5f;
  256. // Set arbitrary input data, but with a point that's extremely far away in space
  257. SetSurfacePoint(input, AZ::Vector3(-100000000.0f), AZ::Vector3(0.0f), {});
  258. // Output should match the input but with the tag added, even though the point was far away.
  259. SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
  260. { AZStd::pair<AZStd::string, float>(tag, gradientValue) });
  261. TestGradientSurfaceDataComponent(
  262. gradientValue, // constant gradient value
  263. 0.1f, // min threshold
  264. 1.0f, // max threshold
  265. { tag }, // supported tags
  266. false, // uses surface bounds?
  267. input,
  268. expectedOutput);
  269. }
  270. TEST_F(GradientSignalSurfaceTestsFixture, GradientSignalSurfaceComponent_ModifyPointInShapeConstraint)
  271. {
  272. // Verify that if a shape constraint is added, points within the shape are still modified.
  273. // Our default mock shape is a cube that exists from -0.5 to 0.5 in space.
  274. AzFramework::SurfaceData::SurfacePoint input;
  275. AzFramework::SurfaceData::SurfacePoint expectedOutput;
  276. const char* tag = "test_mask";
  277. // Select a gradient value within the threshold range below
  278. float gradientValue = 0.5f;
  279. // Set arbitrary input data, but with a point that's within the mock shape cube (0.25 vs -0.5 to 0.5)
  280. SetSurfacePoint(input, AZ::Vector3(0.25f), AZ::Vector3(0.0f), {});
  281. // Output should match the input but with the tag added, since the point is within the shape constraint.
  282. SetSurfacePoint(expectedOutput, input.m_position, input.m_normal,
  283. { AZStd::pair<AZStd::string, float>(tag, gradientValue) });
  284. TestGradientSurfaceDataComponent(
  285. gradientValue, // constant gradient value
  286. 0.1f, // min threshold
  287. 1.0f, // max threshold
  288. { tag }, // supported tags
  289. true, // uses surface bounds?
  290. input,
  291. expectedOutput);
  292. }
  293. TEST_F(GradientSignalSurfaceTestsFixture, GradientSignalSurfaceComponent_DoNotModifyPointOutsideShapeConstraint)
  294. {
  295. // Verify that if a shape constraint is added, points outside the shape are not modified.
  296. // Our default mock shape is a cube that exists from -0.5 to 0.5 in space.
  297. AzFramework::SurfaceData::SurfacePoint input;
  298. AzFramework::SurfaceData::SurfacePoint expectedOutput;
  299. const char* tag = "test_mask";
  300. // Select a gradient value within the threshold range below
  301. float gradientValue = 0.5f;
  302. // Set arbitrary input data, but with a point that's outside the mock shape cube (10.0 vs -0.5 to 0.5)
  303. SetSurfacePoint(input, AZ::Vector3(10.0f), AZ::Vector3(0.0f), {});
  304. // Output should match the input with no tag added, since the point is outside the shape constraint
  305. SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, {});
  306. TestGradientSurfaceDataComponent(
  307. gradientValue, // constant gradient value
  308. 0.1f, // min threshold
  309. 1.0f, // max threshold
  310. { tag }, // supported tags
  311. true, // uses surface bounds?
  312. input,
  313. expectedOutput);
  314. }
  315. }