Geometry2DUtils.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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/Math/Vector2.h>
  9. #include <AzCore/Math/Geometry2DUtils.h>
  10. #include <AzCore/UnitTest/TestTypes.h>
  11. #include <AzCore/std/containers/vector.h>
  12. namespace UnitTest
  13. {
  14. using namespace AZ;
  15. using Geometry2DUtilsFixture = ::UnitTest::LeakDetectionFixture;
  16. struct Signed2DTriangleAreaTestData
  17. {
  18. const float tolerance = 1e-3f;
  19. const Vector2 a = Vector2(0.1f, 0.2f);
  20. const Vector2 b = Vector2(0.3f, 0.5f);
  21. const Vector2 c = Vector2(-0.2f, 0.3f);
  22. const Vector2 d = Vector2(-0.2f, 0.7f);
  23. const Vector2 e = Vector2(-0.2f, 1.0f);
  24. };
  25. TEST_F(Geometry2DUtilsFixture, Signed2DTriangleArea_CounterclockwiseTrianglesReturnCorrectPositiveArea)
  26. {
  27. Signed2DTriangleAreaTestData data;
  28. EXPECT_NEAR(Geometry2DUtils::Signed2DTriangleArea(data.a, data.b, data.c), 0.055f, data.tolerance);
  29. EXPECT_NEAR(Geometry2DUtils::Signed2DTriangleArea(data.b, data.d, data.c), 0.1f, data.tolerance);
  30. EXPECT_NEAR(Geometry2DUtils::Signed2DTriangleArea(data.a, data.d, data.c), 0.06f, data.tolerance);
  31. EXPECT_NEAR(Geometry2DUtils::Signed2DTriangleArea(data.a, data.b, data.d), 0.095f, data.tolerance);
  32. }
  33. TEST_F(Geometry2DUtilsFixture, Signed2DTriangleArea_ClockwiseTriangleReturnsCorrectNegativeArea)
  34. {
  35. Signed2DTriangleAreaTestData data;
  36. EXPECT_NEAR(Geometry2DUtils::Signed2DTriangleArea(data.a, data.c, data.b), -0.055f, data.tolerance);
  37. }
  38. TEST_F(Geometry2DUtilsFixture, Signed2DTriangleArea_TriangleWithTwoIdenticalPointsReturnsZeroArea)
  39. {
  40. Signed2DTriangleAreaTestData data;
  41. EXPECT_NEAR(Geometry2DUtils::Signed2DTriangleArea(data.a, data.b, data.b), 0.0f, data.tolerance);
  42. }
  43. TEST_F(Geometry2DUtilsFixture, Signed2DTriangleArea_TriangleWithThreeCollinearPointsReturnsZeroArea)
  44. {
  45. Signed2DTriangleAreaTestData data;
  46. EXPECT_NEAR(Geometry2DUtils::Signed2DTriangleArea(data.c, data.d, data.e), 0.0f, data.tolerance);
  47. }
  48. struct ShortestDistanceSqPointSegmentTestData
  49. {
  50. float tolerance = 1e-6f;
  51. const Vector2 segmentStart = Vector2(-0.4f, -0.1f);
  52. const Vector2 segmentEnd = Vector2(0.6f, 0.3f);
  53. const AZ::Vector2 point1 = AZ::Vector2(-0.8f, 0.2f);
  54. const AZ::Vector2 point2 = AZ::Vector2(0.72f, 0.25f);
  55. const float expectedDistanceSqFromOrigin = (0.06f * 0.06f) / (1.0f * 1.0f + 0.4f * 0.4f);
  56. const float expectedDistanceSqFromPoint1 = 0.5f * 0.5f;
  57. const float expectedDistanceSqFromPoint2 = 0.13f * 0.13f;
  58. };
  59. TEST_F(Geometry2DUtilsFixture, ShortestDistanceSqPointSegment_PointOnLineReturnsZero)
  60. {
  61. ShortestDistanceSqPointSegmentTestData data;
  62. const Vector2 point(0.1f, 0.1f);
  63. const float distanceSq = Geometry2DUtils::ShortestDistanceSqPointSegment(point, data.segmentStart, data.segmentEnd);
  64. EXPECT_NEAR(distanceSq, 0.0f, data.tolerance);
  65. }
  66. TEST_F(Geometry2DUtilsFixture, ShortestDistanceSqPointSegment_PointWhichProjectsInsideSegmentReturnsOrthogonalDistanceSq)
  67. {
  68. ShortestDistanceSqPointSegmentTestData data;
  69. const Vector2 point = Vector2::CreateZero();
  70. const float distanceSq = Geometry2DUtils::ShortestDistanceSqPointSegment(point, data.segmentStart, data.segmentEnd);
  71. EXPECT_NEAR(distanceSq, data.expectedDistanceSqFromOrigin, data.tolerance);
  72. }
  73. TEST_F(Geometry2DUtilsFixture, ShortestDistanceSqPointSegment_ReversingSegmentStartAndEndDoesNotAffectResult)
  74. {
  75. ShortestDistanceSqPointSegmentTestData data;
  76. const Vector2 point = Vector2::CreateZero();
  77. const float distanceSq = Geometry2DUtils::ShortestDistanceSqPointSegment(point, data.segmentEnd, data.segmentStart);
  78. EXPECT_NEAR(distanceSq, data.expectedDistanceSqFromOrigin, data.tolerance);
  79. }
  80. TEST_F(Geometry2DUtilsFixture, ShortestDistanceSqPointSegment_PointsWhichProjectOutsideSegmentReturnCorrectDistanceSq)
  81. {
  82. ShortestDistanceSqPointSegmentTestData data;
  83. // this point should be closest to segmentStart
  84. const float distanceSq1 = Geometry2DUtils::ShortestDistanceSqPointSegment(data.point1, data.segmentStart, data.segmentEnd);
  85. EXPECT_NEAR(distanceSq1, data.expectedDistanceSqFromPoint1, data.tolerance);
  86. // this point should be closest to segmentEnd
  87. const float distanceSq2 = Geometry2DUtils::ShortestDistanceSqPointSegment(data.point2, data.segmentStart, data.segmentEnd);
  88. EXPECT_NEAR(distanceSq2, data.expectedDistanceSqFromPoint2, data.tolerance);
  89. }
  90. TEST_F(Geometry2DUtilsFixture, ShortestDistanceSqPointSegment_ExtremelyShortLineSegmentsReturnCorrectDistanceSq)
  91. {
  92. ShortestDistanceSqPointSegmentTestData data;
  93. // extremely short segment
  94. const float distanceSq1 = Geometry2DUtils::ShortestDistanceSqPointSegment(data.point1, data.segmentStart,
  95. data.segmentStart + AZ::Vector2(1e-5f, 1e-5f));
  96. EXPECT_NEAR(distanceSq1, data.expectedDistanceSqFromPoint1, data.tolerance);
  97. // zero length segment
  98. const float distanceSq2 = Geometry2DUtils::ShortestDistanceSqPointSegment(data.point1, data.segmentStart, data.segmentStart);
  99. EXPECT_NEAR(distanceSq2, data.expectedDistanceSqFromPoint1, data.tolerance);
  100. }
  101. struct ShortestDistanceSqSegmentSegmentTestData
  102. {
  103. const float tolerance = 1e-6f;
  104. const Vector2 segment1Start = Vector2(0.2f, 0.4f);
  105. const Vector2 segment1End = Vector2(0.44f, 0.47f);
  106. const Vector2 segment2Start = Vector2(0.3f, 0.3f);
  107. const Vector2 segment2End = Vector2(0.4f, 0.54f);
  108. const Vector2 segment3Start = Vector2(0.25f, 0.18f);
  109. const Vector2 segment3End = Vector2(0.35f, 0.42f);
  110. const Vector2 segment4Start = Vector2(0.15f, -0.06f);
  111. const Vector2 segment4End = segment3Start;
  112. const Vector2 segment5Start = Vector2(0.2f, 0.3f);
  113. const Vector2 segment5End = Vector2(0.44f, 0.37f);
  114. // segments 2 and 4 are collinear but do not overlap, so the shortest distance should be from the end
  115. // of segment 4 to the start of segment 2.
  116. const float expectedDistanceSqSegment2Segment4 = (0.05f * 0.05f + 0.12f * 0.12f);
  117. // segments 1 and 5 are parallel and their projections overlap, so the shortest distance should be
  118. // in the directional orthogonal to both segments
  119. const float expectedDistanceSqSegment1Segment5 = (0.1f * 0.1f) * (0.24f * 0.24f) / (0.25f * 0.25f);
  120. // the shortest distance between segment 1 and segment 3 should be between the end of segment 3 and
  121. // an internal point of segment 1
  122. const float expectedDistanceSegment1Segment3 = (0.24f / 0.25f) * (0.4f + 0.07f * 0.15f / 0.24f - 0.42f);
  123. const float expectedDistanceSqSegment1Segment3 = expectedDistanceSegment1Segment3 * expectedDistanceSegment1Segment3;
  124. };
  125. TEST_F(Geometry2DUtilsFixture, ShortestDistanceSqSegmentSegment_IntersectingSegmentsReturnZero)
  126. {
  127. ShortestDistanceSqSegmentSegmentTestData data;
  128. const float distanceSq = Geometry2DUtils::ShortestDistanceSqSegmentSegment(
  129. data.segment1Start, data.segment1End, data.segment2Start, data.segment2End);
  130. EXPECT_NEAR(distanceSq, 0.0f, data.tolerance);
  131. }
  132. TEST_F(Geometry2DUtilsFixture, ShortestDistanceSqSegmentSegment_OverlappingCollinearSegmentsReturnZero)
  133. {
  134. ShortestDistanceSqSegmentSegmentTestData data;
  135. const float distanceSq = Geometry2DUtils::ShortestDistanceSqSegmentSegment(
  136. data.segment2Start, data.segment2End, data.segment3Start, data.segment3End);
  137. EXPECT_NEAR(distanceSq, 0.0f, data.tolerance);
  138. }
  139. TEST_F(Geometry2DUtilsFixture, ShortestDistanceSqSegmentSegment_NonOverlappingCollinearSegmentsReturnCorrectDistanceSq)
  140. {
  141. ShortestDistanceSqSegmentSegmentTestData data;
  142. const float distanceSq = Geometry2DUtils::ShortestDistanceSqSegmentSegment(
  143. data.segment2Start, data.segment2End, data.segment4Start, data.segment4End);
  144. EXPECT_NEAR(distanceSq, data.expectedDistanceSqSegment2Segment4, data.tolerance);
  145. }
  146. TEST_F(Geometry2DUtilsFixture, ShortestDistanceSqSegmentSegment_ParallelSegmentsWithOverlappingProjectionsReturnCorrectDistanceSq)
  147. {
  148. ShortestDistanceSqSegmentSegmentTestData data;
  149. const float distanceSq = Geometry2DUtils::ShortestDistanceSqSegmentSegment(
  150. data.segment1Start, data.segment1End, data.segment5Start, data.segment5End);
  151. EXPECT_NEAR(distanceSq, data.expectedDistanceSqSegment1Segment5, data.tolerance);
  152. }
  153. TEST_F(Geometry2DUtilsFixture, ShortestDistanceSqSegmentSegment_EndOfOneSegmentClosestToInternalPointOfOtherSegmentReturnsCorrectDistanceSq)
  154. {
  155. ShortestDistanceSqSegmentSegmentTestData data;
  156. const float distanceSq = Geometry2DUtils::ShortestDistanceSqSegmentSegment(
  157. data.segment1Start, data.segment1End, data.segment3Start, data.segment3End);
  158. EXPECT_NEAR(distanceSq, data.expectedDistanceSqSegment1Segment3, data.tolerance);
  159. }
  160. TEST_F(Geometry2DUtilsFixture, ShortestDistanceSqSegmentSegment_ReversingOrderOfSegmentsDoesNotAffectResult)
  161. {
  162. ShortestDistanceSqSegmentSegmentTestData data;
  163. // reverse the order of the segments
  164. const float distanceSq1 = Geometry2DUtils::ShortestDistanceSqSegmentSegment(
  165. data.segment3Start, data.segment3End, data.segment1Start, data.segment1End);
  166. EXPECT_NEAR(distanceSq1, data.expectedDistanceSqSegment1Segment3, data.tolerance);
  167. // reverse the start and end of one of the segments
  168. const float distanceSq2 = Geometry2DUtils::ShortestDistanceSqSegmentSegment(
  169. data.segment3End, data.segment3Start, data.segment1Start, data.segment1End);
  170. EXPECT_NEAR(distanceSq2, data.expectedDistanceSqSegment1Segment3, data.tolerance);
  171. }
  172. struct PolygonTestData
  173. {
  174. const AZStd::vector<Vector2> polygonHShape = {
  175. Vector2(0.0f, 0.0f),
  176. Vector2(0.0f, 3.0f),
  177. Vector2(1.0f, 3.0f),
  178. Vector2(1.0f, 2.0f),
  179. Vector2(2.0f, 2.0f),
  180. Vector2(2.0f, 3.0f),
  181. Vector2(3.0f, 3.0f),
  182. Vector2(3.0f, 0.0f),
  183. Vector2(2.0f, 0.0f),
  184. Vector2(2.0f, 1.0f),
  185. Vector2(1.0f, 1.0f),
  186. Vector2(1.0f, 0.0f)
  187. };
  188. const AZStd::vector<Vector2> pentagon = {
  189. Vector2(0.0f, 0.0f),
  190. Vector2(1.0f, 2.0f),
  191. Vector2(-1.0f, 3.0f),
  192. Vector2(-3.0f, 2.0f),
  193. Vector2(-2.0f, 0.0f)
  194. };
  195. const AZStd::vector<Vector2> pentagram = {
  196. Vector2(0.0f, 0.0f),
  197. Vector2(-1.0f, 3.0f),
  198. Vector2(-2.0f, 0.0f),
  199. Vector2(1.0f, 2.0f),
  200. Vector2(-3.0f, 2.0f)
  201. };
  202. // a shape with 2 saw teeth, with the notch extremely close to the long edge
  203. const AZStd::vector<Vector2> sawShape = {
  204. Vector2(0.0f, 0.0f),
  205. Vector2(-1.0f, 2.0f),
  206. Vector2(-2.0f, 1e-6f),
  207. Vector2(-3.0f, 2.0f),
  208. Vector2(-4.0f, 0.0f)
  209. };
  210. // two diamonds connected at the corners, with two extremely close vertices where the diamonds meet
  211. const AZStd::vector<Vector2> doubleDiamond = {
  212. Vector2(0.0f, 0.0f),
  213. Vector2(-1.0f, 1.0f),
  214. Vector2(-2.0f, 1e-6f),
  215. Vector2(-3.0f, 1.0f),
  216. Vector2(-4.0f, 0.0f),
  217. Vector2(-3.0f, -1.0f),
  218. Vector2(-2.0f, -1e-6f),
  219. Vector2(-1.0f, -1.0f)
  220. };
  221. };
  222. TEST_F(Geometry2DUtilsFixture, IsSimplePolygon_HShapedPolygonReturnsTrue)
  223. {
  224. PolygonTestData data;
  225. EXPECT_TRUE(Geometry2DUtils::IsSimplePolygon(data.polygonHShape));
  226. // reversing the winding order should not affect the result here
  227. AZStd::vector<Vector2> pointsReversed(data.polygonHShape.size());
  228. AZStd::reverse_copy(data.polygonHShape.begin(), data.polygonHShape.end(), pointsReversed.begin());
  229. EXPECT_TRUE(Geometry2DUtils::IsSimplePolygon(pointsReversed));
  230. }
  231. TEST_F(Geometry2DUtilsFixture, IsSimplePolygon_PentagonReturnsTrue)
  232. {
  233. PolygonTestData data;
  234. EXPECT_TRUE(Geometry2DUtils::IsSimplePolygon(data.pentagon));
  235. }
  236. TEST_F(Geometry2DUtilsFixture, IsSimplePolygon_PentagramReturnsFalse)
  237. {
  238. PolygonTestData data;
  239. EXPECT_FALSE(Geometry2DUtils::IsSimplePolygon(data.pentagram));
  240. }
  241. TEST_F(Geometry2DUtilsFixture, IsSimplePolygon_AlmostIntersectingPolygonReturnsFalse)
  242. {
  243. PolygonTestData data;
  244. EXPECT_FALSE(Geometry2DUtils::IsSimplePolygon(data.sawShape));
  245. }
  246. TEST_F(Geometry2DUtilsFixture, IsSimplePolygon_PolygonWithTwoExtremelyCloseVerticesReturnsFalse)
  247. {
  248. PolygonTestData data;
  249. EXPECT_FALSE(Geometry2DUtils::IsSimplePolygon(data.doubleDiamond));
  250. }
  251. TEST_F(Geometry2DUtilsFixture, IsConvex_HShapedPolygonReturnsFalse)
  252. {
  253. PolygonTestData data;
  254. EXPECT_FALSE(Geometry2DUtils::IsConvex(data.polygonHShape));
  255. }
  256. TEST_F(Geometry2DUtilsFixture, IsConvex_PentagonReturnsTrue)
  257. {
  258. PolygonTestData data;
  259. EXPECT_TRUE(Geometry2DUtils::IsConvex(data.pentagon));
  260. }
  261. TEST_F(Geometry2DUtilsFixture, IsConvex_PentagramReturnsTrue)
  262. {
  263. PolygonTestData data;
  264. EXPECT_TRUE(Geometry2DUtils::IsConvex(data.pentagram));
  265. }
  266. TEST_F(Geometry2DUtilsFixture, IsConvex_PolygonWithNotchReturnsFalse)
  267. {
  268. PolygonTestData data;
  269. EXPECT_FALSE(Geometry2DUtils::IsConvex(data.sawShape));
  270. }
  271. } // namespace UnitTest