test_geometry_2d.h 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. /**************************************************************************/
  2. /* test_geometry_2d.h */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #pragma once
  31. #include "core/math/geometry_2d.h"
  32. #include "thirdparty/doctest/doctest.h"
  33. namespace TestGeometry2D {
  34. TEST_CASE("[Geometry2D] Point in circle") {
  35. CHECK(Geometry2D::is_point_in_circle(Vector2(0, 0), Vector2(0, 0), 1.0));
  36. CHECK(Geometry2D::is_point_in_circle(Vector2(0, 0), Vector2(11.99, 0), 12));
  37. CHECK(Geometry2D::is_point_in_circle(Vector2(-11.99, 0), Vector2(0, 0), 12));
  38. CHECK_FALSE(Geometry2D::is_point_in_circle(Vector2(0, 0), Vector2(12.01, 0), 12));
  39. CHECK_FALSE(Geometry2D::is_point_in_circle(Vector2(-12.01, 0), Vector2(0, 0), 12));
  40. CHECK(Geometry2D::is_point_in_circle(Vector2(7, -42), Vector2(4, -40), 3.7));
  41. CHECK_FALSE(Geometry2D::is_point_in_circle(Vector2(7, -42), Vector2(4, -40), 3.5));
  42. // This tests points on the edge of the circle. They are treated as being inside the circle.
  43. CHECK(Geometry2D::is_point_in_circle(Vector2(1.0, 0.0), Vector2(0, 0), 1.0));
  44. CHECK(Geometry2D::is_point_in_circle(Vector2(0.0, -1.0), Vector2(0, 0), 1.0));
  45. }
  46. TEST_CASE("[Geometry2D] Point in triangle") {
  47. CHECK(Geometry2D::is_point_in_triangle(Vector2(0, 0), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
  48. CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(-1.01, 1.0), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
  49. CHECK(Geometry2D::is_point_in_triangle(Vector2(3, 2.5), Vector2(1, 4), Vector2(3, 2), Vector2(5, 4)));
  50. CHECK(Geometry2D::is_point_in_triangle(Vector2(-3, -2.5), Vector2(-1, -4), Vector2(-3, -2), Vector2(-5, -4)));
  51. CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(0, 0), Vector2(1, 4), Vector2(3, 2), Vector2(5, 4)));
  52. // This tests points on the edge of the triangle. They are treated as being outside the triangle.
  53. // In `is_point_in_circle` and `is_point_in_polygon` they are treated as being inside, so in order the make
  54. // the behavior consistent this may change in the future (see issue #44717 and PR #44274).
  55. CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(1, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
  56. CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(0, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
  57. }
  58. TEST_CASE("[Geometry2D] Point in polygon") {
  59. Vector<Vector2> p;
  60. CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(0, 0), p));
  61. p.push_back(Vector2(-88, 120));
  62. p.push_back(Vector2(-74, -38));
  63. p.push_back(Vector2(135, -145));
  64. p.push_back(Vector2(425, 70));
  65. p.push_back(Vector2(68, 112));
  66. p.push_back(Vector2(-120, 370));
  67. p.push_back(Vector2(-323, -145));
  68. CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(-350, 0), p));
  69. CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(-110, 60), p));
  70. CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(412, 96), p));
  71. CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(83, 130), p));
  72. CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(-320, -153), p));
  73. CHECK(Geometry2D::is_point_in_polygon(Vector2(0, 0), p));
  74. CHECK(Geometry2D::is_point_in_polygon(Vector2(-230, 0), p));
  75. CHECK(Geometry2D::is_point_in_polygon(Vector2(130, -110), p));
  76. CHECK(Geometry2D::is_point_in_polygon(Vector2(370, 55), p));
  77. CHECK(Geometry2D::is_point_in_polygon(Vector2(-160, 190), p));
  78. // This tests points on the edge of the polygon. They are treated as being inside the polygon.
  79. int c = p.size();
  80. for (int i = 0; i < c; i++) {
  81. const Vector2 &p1 = p[i];
  82. CHECK(Geometry2D::is_point_in_polygon(p1, p));
  83. const Vector2 &p2 = p[(i + 1) % c];
  84. Vector2 midpoint((p1 + p2) * 0.5);
  85. CHECK(Geometry2D::is_point_in_polygon(midpoint, p));
  86. }
  87. }
  88. TEST_CASE("[Geometry2D] Polygon clockwise") {
  89. Vector<Vector2> p;
  90. CHECK_FALSE(Geometry2D::is_polygon_clockwise(p));
  91. p.push_back(Vector2(5, -5));
  92. p.push_back(Vector2(-1, -5));
  93. p.push_back(Vector2(-5, -1));
  94. p.push_back(Vector2(-1, 3));
  95. p.push_back(Vector2(1, 5));
  96. CHECK(Geometry2D::is_polygon_clockwise(p));
  97. p.reverse();
  98. CHECK_FALSE(Geometry2D::is_polygon_clockwise(p));
  99. }
  100. TEST_CASE("[Geometry2D] Line intersection") {
  101. Vector2 r;
  102. CHECK(Geometry2D::line_intersects_line(Vector2(2, 0), Vector2(0, 1), Vector2(0, 2), Vector2(1, 0), r));
  103. CHECK(r.is_equal_approx(Vector2(2, 2)));
  104. CHECK(Geometry2D::line_intersects_line(Vector2(-1, 1), Vector2(1, -1), Vector2(4, 1), Vector2(-1, -1), r));
  105. CHECK(r.is_equal_approx(Vector2(1.5, -1.5)));
  106. CHECK(Geometry2D::line_intersects_line(Vector2(-1, 0), Vector2(-1, -1), Vector2(1, 0), Vector2(1, -1), r));
  107. CHECK(r.is_equal_approx(Vector2(0, 1)));
  108. CHECK_FALSE_MESSAGE(
  109. Geometry2D::line_intersects_line(Vector2(-1, 1), Vector2(1, -1), Vector2(0, 1), Vector2(1, -1), r),
  110. "Parallel lines should not intersect.");
  111. }
  112. TEST_CASE("[Geometry2D] Segment intersection") {
  113. Vector2 r;
  114. CHECK(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(-1, -1), &r));
  115. CHECK(r.is_equal_approx(Vector2(0, 0)));
  116. CHECK_FALSE(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(0.1, 0.1), &r));
  117. CHECK_FALSE(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0.1, 0.1), Vector2(1, 1), &r));
  118. CHECK_FALSE_MESSAGE(
  119. Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0, 1), Vector2(2, -1), &r),
  120. "Parallel segments should not intersect.");
  121. CHECK_FALSE_MESSAGE(
  122. Geometry2D::segment_intersects_segment(Vector2(1, 2), Vector2(3, 2), Vector2(0, 2), Vector2(-2, 2), &r),
  123. "Non-overlapping collinear segments should not intersect.");
  124. CHECK_MESSAGE(
  125. Geometry2D::segment_intersects_segment(Vector2(0, 0), Vector2(0, 1), Vector2(0, 0), Vector2(1, 0), &r),
  126. "Touching segments should intersect.");
  127. CHECK(r.is_equal_approx(Vector2(0, 0)));
  128. CHECK_MESSAGE(
  129. Geometry2D::segment_intersects_segment(Vector2(0, 1), Vector2(0, 0), Vector2(0, 0), Vector2(1, 0), &r),
  130. "Touching segments should intersect.");
  131. CHECK(r.is_equal_approx(Vector2(0, 0)));
  132. }
  133. TEST_CASE("[Geometry2D] Segment intersection with circle") {
  134. constexpr real_t minus_one = -1.0;
  135. constexpr real_t zero = 0.0;
  136. constexpr real_t one_quarter = 0.25;
  137. constexpr real_t three_quarters = 0.75;
  138. constexpr real_t one = 1.0;
  139. CHECK_MESSAGE(
  140. Geometry2D::segment_intersects_circle(Vector2(0, 0), Vector2(4, 0), Vector2(0, 0), 1.0) == doctest::Approx(one_quarter),
  141. "Segment from inside to outside of circle should intersect it.");
  142. CHECK_MESSAGE(
  143. Geometry2D::segment_intersects_circle(Vector2(4, 0), Vector2(0, 0), Vector2(0, 0), 1.0) == doctest::Approx(three_quarters),
  144. "Segment from outside to inside of circle should intersect it.");
  145. CHECK_MESSAGE(
  146. Geometry2D::segment_intersects_circle(Vector2(-2, 0), Vector2(2, 0), Vector2(0, 0), 1.0) == doctest::Approx(one_quarter),
  147. "Segment running through circle should intersect it.");
  148. CHECK_MESSAGE(
  149. Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(-2, 0), Vector2(0, 0), 1.0) == doctest::Approx(one_quarter),
  150. "Segment running through circle should intersect it.");
  151. CHECK_MESSAGE(
  152. Geometry2D::segment_intersects_circle(Vector2(0, 0), Vector2(1, 0), Vector2(0, 0), 1.0) == doctest::Approx(one),
  153. "Segment starting inside the circle and ending on the circle should intersect it");
  154. CHECK_MESSAGE(
  155. Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(0, 0), Vector2(0, 0), 1.0) == doctest::Approx(zero),
  156. "Segment starting on the circle and going inwards should intersect it");
  157. CHECK_MESSAGE(
  158. Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(2, 0), Vector2(0, 0), 1.0) == doctest::Approx(zero),
  159. "Segment starting on the circle and going outwards should intersect it");
  160. CHECK_MESSAGE(
  161. Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(1, 0), Vector2(0, 0), 1.0) == doctest::Approx(one),
  162. "Segment starting outside the circle and ending on the circle intersect it");
  163. CHECK_MESSAGE(
  164. Geometry2D::segment_intersects_circle(Vector2(-1, 0), Vector2(1, 0), Vector2(0, 0), 2.0) == doctest::Approx(minus_one),
  165. "Segment completely within the circle should not intersect it");
  166. CHECK_MESSAGE(
  167. Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(-1, 0), Vector2(0, 0), 2.0) == doctest::Approx(minus_one),
  168. "Segment completely within the circle should not intersect it");
  169. CHECK_MESSAGE(
  170. Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(3, 0), Vector2(0, 0), 1.0) == doctest::Approx(minus_one),
  171. "Segment completely outside the circle should not intersect it");
  172. CHECK_MESSAGE(
  173. Geometry2D::segment_intersects_circle(Vector2(3, 0), Vector2(2, 0), Vector2(0, 0), 1.0) == doctest::Approx(minus_one),
  174. "Segment completely outside the circle should not intersect it");
  175. }
  176. TEST_CASE("[Geometry2D] Segment intersection with polygon") {
  177. Vector<Point2> a;
  178. a.push_back(Point2(-2, 2));
  179. a.push_back(Point2(3, 4));
  180. a.push_back(Point2(1, 1));
  181. a.push_back(Point2(2, -2));
  182. a.push_back(Point2(-1, -1));
  183. CHECK_MESSAGE(
  184. Geometry2D::is_segment_intersecting_polygon(Vector2(0, 2), Vector2(2, 2), a),
  185. "Segment from inside to outside of polygon should intersect it.");
  186. CHECK_MESSAGE(
  187. Geometry2D::is_segment_intersecting_polygon(Vector2(2, 2), Vector2(0, 2), a),
  188. "Segment from outside to inside of polygon should intersect it.");
  189. CHECK_MESSAGE(
  190. Geometry2D::is_segment_intersecting_polygon(Vector2(2, 4), Vector2(3, 3), a),
  191. "Segment running through polygon should intersect it.");
  192. CHECK_MESSAGE(
  193. Geometry2D::is_segment_intersecting_polygon(Vector2(3, 3), Vector2(2, 4), a),
  194. "Segment running through polygon should intersect it.");
  195. CHECK_MESSAGE(
  196. Geometry2D::is_segment_intersecting_polygon(Vector2(0, 0), Vector2(1, 1), a),
  197. "Segment starting inside the polygon and ending on the polygon should intersect it");
  198. CHECK_MESSAGE(
  199. Geometry2D::is_segment_intersecting_polygon(Vector2(1, 1), Vector2(0, 0), a),
  200. "Segment starting on the polygon and going inwards should intersect it");
  201. CHECK_MESSAGE(
  202. Geometry2D::is_segment_intersecting_polygon(Vector2(-2, 2), Vector2(-2, -1), a),
  203. "Segment starting on the polygon and going outwards should intersect it");
  204. CHECK_MESSAGE(
  205. Geometry2D::is_segment_intersecting_polygon(Vector2(-2, 1), Vector2(-2, 2), a),
  206. "Segment starting outside the polygon and ending on the polygon intersect it");
  207. CHECK_FALSE_MESSAGE(
  208. Geometry2D::is_segment_intersecting_polygon(Vector2(-1, 2), Vector2(1, -1), a),
  209. "Segment completely within the polygon should not intersect it");
  210. CHECK_FALSE_MESSAGE(
  211. Geometry2D::is_segment_intersecting_polygon(Vector2(1, -1), Vector2(-1, 2), a),
  212. "Segment completely within the polygon should not intersect it");
  213. CHECK_FALSE_MESSAGE(
  214. Geometry2D::is_segment_intersecting_polygon(Vector2(2, 2), Vector2(2, -1), a),
  215. "Segment completely outside the polygon should not intersect it");
  216. CHECK_FALSE_MESSAGE(
  217. Geometry2D::is_segment_intersecting_polygon(Vector2(2, -1), Vector2(2, 2), a),
  218. "Segment completely outside the polygon should not intersect it");
  219. }
  220. TEST_CASE("[Geometry2D] Closest point to segment") {
  221. Vector2 a = Vector2(-4, -4);
  222. Vector2 b = Vector2(4, 4);
  223. CHECK(Geometry2D::get_closest_point_to_segment(Vector2(4.1, 4.1), a, b).is_equal_approx(Vector2(4, 4)));
  224. CHECK(Geometry2D::get_closest_point_to_segment(Vector2(-4.1, -4.1), a, b).is_equal_approx(Vector2(-4, -4)));
  225. CHECK(Geometry2D::get_closest_point_to_segment(Vector2(-1, 1), a, b).is_equal_approx(Vector2(0, 0)));
  226. a = Vector2(1, -2);
  227. b = Vector2(1, -2);
  228. CHECK_MESSAGE(
  229. Geometry2D::get_closest_point_to_segment(Vector2(-3, 4), a, b).is_equal_approx(Vector2(1, -2)),
  230. "Line segment is only a single point. This point should be the closest.");
  231. }
  232. TEST_CASE("[Geometry2D] Closest point to uncapped segment") {
  233. constexpr Vector2 a = Vector2(-4, -4);
  234. constexpr Vector2 b = Vector2(4, 4);
  235. CHECK(Geometry2D::get_closest_point_to_segment_uncapped(Vector2(-1, 1), a, b).is_equal_approx(Vector2(0, 0)));
  236. CHECK(Geometry2D::get_closest_point_to_segment_uncapped(Vector2(-4, -6), a, b).is_equal_approx(Vector2(-5, -5)));
  237. CHECK(Geometry2D::get_closest_point_to_segment_uncapped(Vector2(4, 6), a, b).is_equal_approx(Vector2(5, 5)));
  238. }
  239. TEST_CASE("[Geometry2D] Closest points between segments") {
  240. Vector2 c1, c2;
  241. // Basis Path Testing suite
  242. SUBCASE("[Geometry2D] Both segments degenerate to a point") {
  243. Geometry2D::get_closest_points_between_segments(Vector2(0, 0), Vector2(0, 0), Vector2(0, 0), Vector2(0, 0), c1, c2);
  244. CHECK(c1.is_equal_approx(Vector2(0, 0)));
  245. CHECK(c2.is_equal_approx(Vector2(0, 0)));
  246. }
  247. SUBCASE("[Geometry2D] Closest point on second segment trajectory is above [0,1]") {
  248. Geometry2D::get_closest_points_between_segments(Vector2(50, -25), Vector2(50, -10), Vector2(-50, 10), Vector2(-40, 10), c1, c2);
  249. CHECK(c1.is_equal_approx(Vector2(50, -10)));
  250. CHECK(c2.is_equal_approx(Vector2(-40, 10)));
  251. }
  252. SUBCASE("[Geometry2D] Parallel segments") {
  253. Geometry2D::get_closest_points_between_segments(Vector2(2, 1), Vector2(4, 3), Vector2(2, 3), Vector2(4, 5), c1, c2);
  254. CHECK(c1.is_equal_approx(Vector2(3, 2)));
  255. CHECK(c2.is_equal_approx(Vector2(2, 3)));
  256. }
  257. SUBCASE("[Geometry2D] Closest point on second segment trajectory is within [0,1]") {
  258. Geometry2D::get_closest_points_between_segments(Vector2(2, 4), Vector2(2, 3), Vector2(1, 1), Vector2(4, 4), c1, c2);
  259. CHECK(c1.is_equal_approx(Vector2(2, 3)));
  260. CHECK(c2.is_equal_approx(Vector2(2.5, 2.5)));
  261. }
  262. SUBCASE("[Geometry2D] Closest point on second segment trajectory is below [0,1]") {
  263. Geometry2D::get_closest_points_between_segments(Vector2(-20, -20), Vector2(-10, -40), Vector2(10, 25), Vector2(25, 40), c1, c2);
  264. CHECK(c1.is_equal_approx(Vector2(-20, -20)));
  265. CHECK(c2.is_equal_approx(Vector2(10, 25)));
  266. }
  267. SUBCASE("[Geometry2D] Second segment degenerates to a point") {
  268. Geometry2D::get_closest_points_between_segments(Vector2(1, 2), Vector2(2, 1), Vector2(3, 3), Vector2(3, 3), c1, c2);
  269. CHECK(c1.is_equal_approx(Vector2(1.5, 1.5)));
  270. CHECK(c2.is_equal_approx(Vector2(3, 3)));
  271. }
  272. SUBCASE("[Geometry2D] First segment degenerates to a point") {
  273. Geometry2D::get_closest_points_between_segments(Vector2(1, 1), Vector2(1, 1), Vector2(2, 2), Vector2(4, 4), c1, c2);
  274. CHECK(c1.is_equal_approx(Vector2(1, 1)));
  275. CHECK(c2.is_equal_approx(Vector2(2, 2)));
  276. }
  277. // End Basis Path Testing suite
  278. SUBCASE("[Geometry2D] Segments are equal vectors") {
  279. Geometry2D::get_closest_points_between_segments(Vector2(2, 2), Vector2(3, 3), Vector2(4, 4), Vector2(4, 5), c1, c2);
  280. CHECK(c1.is_equal_approx(Vector2(3, 3)));
  281. CHECK(c2.is_equal_approx(Vector2(4, 4)));
  282. }
  283. SUBCASE("[Geometry2D] Standard case") {
  284. Geometry2D::get_closest_points_between_segments(Vector2(0, 1), Vector2(-2, -1), Vector2(0, 0), Vector2(2, -2), c1, c2);
  285. CHECK(c1.is_equal_approx(Vector2(-0.5, 0.5)));
  286. CHECK(c2.is_equal_approx(Vector2(0, 0)));
  287. }
  288. SUBCASE("[Geometry2D] Segments intersect") {
  289. Geometry2D::get_closest_points_between_segments(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(-1, -1), c1, c2);
  290. CHECK(c1.is_equal_approx(Vector2(0, 0)));
  291. CHECK(c2.is_equal_approx(Vector2(0, 0)));
  292. }
  293. }
  294. TEST_CASE("[Geometry2D] Make atlas") {
  295. Vector<Point2i> result;
  296. Size2i size;
  297. Vector<Size2i> r;
  298. r.push_back(Size2i(2, 2));
  299. Geometry2D::make_atlas(r, result, size);
  300. CHECK(size == Size2i(2, 2));
  301. CHECK(result.size() == r.size());
  302. r.clear();
  303. result.clear();
  304. r.push_back(Size2i(1, 2));
  305. r.push_back(Size2i(3, 4));
  306. r.push_back(Size2i(5, 6));
  307. r.push_back(Size2i(7, 8));
  308. Geometry2D::make_atlas(r, result, size);
  309. CHECK(result.size() == r.size());
  310. }
  311. TEST_CASE("[Geometry2D] Polygon intersection") {
  312. Vector<Point2> a;
  313. Vector<Point2> b;
  314. Vector<Vector<Point2>> r;
  315. a.push_back(Point2(30, 60));
  316. a.push_back(Point2(70, 5));
  317. a.push_back(Point2(200, 40));
  318. a.push_back(Point2(80, 200));
  319. SUBCASE("[Geometry2D] Both polygons are empty") {
  320. r = Geometry2D::intersect_polygons(Vector<Point2>(), Vector<Point2>());
  321. CHECK_MESSAGE(r.is_empty(), "Both polygons are empty. The intersection should also be empty.");
  322. }
  323. SUBCASE("[Geometry2D] One polygon is empty") {
  324. r = Geometry2D::intersect_polygons(a, b);
  325. REQUIRE_MESSAGE(r.is_empty(), "One polygon is empty. The intersection should also be empty.");
  326. }
  327. SUBCASE("[Geometry2D] Basic intersection") {
  328. b.push_back(Point2(200, 300));
  329. b.push_back(Point2(90, 200));
  330. b.push_back(Point2(50, 100));
  331. b.push_back(Point2(200, 90));
  332. r = Geometry2D::intersect_polygons(a, b);
  333. REQUIRE_MESSAGE(r.size() == 1, "The polygons should intersect each other with 1 resulting intersection polygon.");
  334. REQUIRE_MESSAGE(r[0].size() == 3, "The resulting intersection polygon should have 3 vertices.");
  335. CHECK(r[0][0].is_equal_approx(Point2(86.52174, 191.30436)));
  336. CHECK(r[0][1].is_equal_approx(Point2(50, 100)));
  337. CHECK(r[0][2].is_equal_approx(Point2(160.52632, 92.63157)));
  338. }
  339. SUBCASE("[Geometry2D] Intersection with one polygon being completely inside the other polygon") {
  340. b.push_back(Point2(80, 100));
  341. b.push_back(Point2(50, 50));
  342. b.push_back(Point2(150, 50));
  343. r = Geometry2D::intersect_polygons(a, b);
  344. REQUIRE_MESSAGE(r.size() == 1, "The polygons should intersect each other with 1 resulting intersection polygon.");
  345. REQUIRE_MESSAGE(r[0].size() == 3, "The resulting intersection polygon should have 3 vertices.");
  346. CHECK(r[0][0].is_equal_approx(b[0]));
  347. CHECK(r[0][1].is_equal_approx(b[1]));
  348. CHECK(r[0][2].is_equal_approx(b[2]));
  349. }
  350. SUBCASE("[Geometry2D] No intersection with 2 non-empty polygons") {
  351. b.push_back(Point2(150, 150));
  352. b.push_back(Point2(250, 100));
  353. b.push_back(Point2(300, 200));
  354. r = Geometry2D::intersect_polygons(a, b);
  355. REQUIRE_MESSAGE(r.is_empty(), "The polygons should not intersect each other.");
  356. }
  357. SUBCASE("[Geometry2D] Intersection with 2 resulting polygons") {
  358. a.clear();
  359. a.push_back(Point2(70, 5));
  360. a.push_back(Point2(140, 7));
  361. a.push_back(Point2(100, 52));
  362. a.push_back(Point2(170, 50));
  363. a.push_back(Point2(60, 125));
  364. b.push_back(Point2(70, 105));
  365. b.push_back(Point2(115, 55));
  366. b.push_back(Point2(90, 15));
  367. b.push_back(Point2(160, 50));
  368. r = Geometry2D::intersect_polygons(a, b);
  369. REQUIRE_MESSAGE(r.size() == 2, "The polygons should intersect each other with 2 resulting intersection polygons.");
  370. REQUIRE_MESSAGE(r[0].size() == 4, "The resulting intersection polygon should have 4 vertices.");
  371. CHECK(r[0][0].is_equal_approx(Point2(70, 105)));
  372. CHECK(r[0][1].is_equal_approx(Point2(115, 55)));
  373. CHECK(r[0][2].is_equal_approx(Point2(112.894737, 51.63158)));
  374. CHECK(r[0][3].is_equal_approx(Point2(159.509537, 50.299728)));
  375. REQUIRE_MESSAGE(r[1].size() == 3, "The intersection polygon should have 3 vertices.");
  376. CHECK(r[1][0].is_equal_approx(Point2(119.692307, 29.846149)));
  377. CHECK(r[1][1].is_equal_approx(Point2(107.706421, 43.33028)));
  378. CHECK(r[1][2].is_equal_approx(Point2(90, 15)));
  379. }
  380. }
  381. TEST_CASE("[Geometry2D] Merge polygons") {
  382. Vector<Point2> a;
  383. Vector<Point2> b;
  384. Vector<Vector<Point2>> r;
  385. a.push_back(Point2(225, 180));
  386. a.push_back(Point2(160, 230));
  387. a.push_back(Point2(20, 212));
  388. a.push_back(Point2(50, 115));
  389. SUBCASE("[Geometry2D] Both polygons are empty") {
  390. r = Geometry2D::merge_polygons(Vector<Point2>(), Vector<Point2>());
  391. REQUIRE_MESSAGE(r.is_empty(), "Both polygons are empty. The union should also be empty.");
  392. }
  393. SUBCASE("[Geometry2D] One polygon is empty") {
  394. r = Geometry2D::merge_polygons(a, b);
  395. REQUIRE_MESSAGE(r.size() == 1, "One polygon is non-empty. There should be 1 resulting merged polygon.");
  396. REQUIRE_MESSAGE(r[0].size() == 4, "The resulting merged polygon should have 4 vertices.");
  397. CHECK(r[0][0].is_equal_approx(a[0]));
  398. CHECK(r[0][1].is_equal_approx(a[1]));
  399. CHECK(r[0][2].is_equal_approx(a[2]));
  400. CHECK(r[0][3].is_equal_approx(a[3]));
  401. }
  402. SUBCASE("[Geometry2D] Basic merge with 2 polygons") {
  403. b.push_back(Point2(180, 190));
  404. b.push_back(Point2(60, 140));
  405. b.push_back(Point2(160, 80));
  406. r = Geometry2D::merge_polygons(a, b);
  407. REQUIRE_MESSAGE(r.size() == 1, "The merged polygons should result in 1 polygon.");
  408. REQUIRE_MESSAGE(r[0].size() == 7, "The resulting merged polygon should have 7 vertices.");
  409. CHECK(r[0][0].is_equal_approx(Point2(174.791077, 161.350967)));
  410. CHECK(r[0][1].is_equal_approx(Point2(225, 180)));
  411. CHECK(r[0][2].is_equal_approx(Point2(160, 230)));
  412. CHECK(r[0][3].is_equal_approx(Point2(20, 212)));
  413. CHECK(r[0][4].is_equal_approx(Point2(50, 115)));
  414. CHECK(r[0][5].is_equal_approx(Point2(81.911758, 126.852943)));
  415. CHECK(r[0][6].is_equal_approx(Point2(160, 80)));
  416. }
  417. SUBCASE("[Geometry2D] Merge with 2 resulting merged polygons (outline and hole)") {
  418. b.push_back(Point2(180, 190));
  419. b.push_back(Point2(140, 125));
  420. b.push_back(Point2(60, 140));
  421. b.push_back(Point2(160, 80));
  422. r = Geometry2D::merge_polygons(a, b);
  423. REQUIRE_MESSAGE(r.size() == 2, "The merged polygons should result in 2 polygons.");
  424. REQUIRE_MESSAGE(!Geometry2D::is_polygon_clockwise(r[0]), "The merged polygon (outline) should be counter-clockwise.");
  425. REQUIRE_MESSAGE(r[0].size() == 7, "The resulting merged polygon (outline) should have 7 vertices.");
  426. CHECK(r[0][0].is_equal_approx(Point2(174.791077, 161.350967)));
  427. CHECK(r[0][1].is_equal_approx(Point2(225, 180)));
  428. CHECK(r[0][2].is_equal_approx(Point2(160, 230)));
  429. CHECK(r[0][3].is_equal_approx(Point2(20, 212)));
  430. CHECK(r[0][4].is_equal_approx(Point2(50, 115)));
  431. CHECK(r[0][5].is_equal_approx(Point2(81.911758, 126.852943)));
  432. CHECK(r[0][6].is_equal_approx(Point2(160, 80)));
  433. REQUIRE_MESSAGE(Geometry2D::is_polygon_clockwise(r[1]), "The resulting merged polygon (hole) should be clockwise.");
  434. REQUIRE_MESSAGE(r[1].size() == 3, "The resulting merged polygon (hole) should have 3 vertices.");
  435. CHECK(r[1][0].is_equal_approx(Point2(98.083069, 132.859421)));
  436. CHECK(r[1][1].is_equal_approx(Point2(158.689453, 155.370377)));
  437. CHECK(r[1][2].is_equal_approx(Point2(140, 125)));
  438. }
  439. }
  440. TEST_CASE("[Geometry2D] Clip polygons") {
  441. Vector<Point2> a;
  442. Vector<Point2> b;
  443. Vector<Vector<Point2>> r;
  444. a.push_back(Point2(225, 180));
  445. a.push_back(Point2(160, 230));
  446. a.push_back(Point2(20, 212));
  447. a.push_back(Point2(50, 115));
  448. SUBCASE("[Geometry2D] Both polygons are empty") {
  449. r = Geometry2D::clip_polygons(Vector<Point2>(), Vector<Point2>());
  450. CHECK_MESSAGE(r.is_empty(), "Both polygons are empty. The clip should also be empty.");
  451. }
  452. SUBCASE("[Geometry2D] Basic clip with one result polygon") {
  453. b.push_back(Point2(250, 170));
  454. b.push_back(Point2(175, 270));
  455. b.push_back(Point2(120, 260));
  456. b.push_back(Point2(25, 80));
  457. r = Geometry2D::clip_polygons(a, b);
  458. REQUIRE_MESSAGE(r.size() == 1, "The clipped polygons should result in 1 polygon.");
  459. REQUIRE_MESSAGE(r[0].size() == 3, "The resulting clipped polygon should have 3 vertices.");
  460. CHECK(r[0][0].is_equal_approx(Point2(100.102173, 222.298843)));
  461. CHECK(r[0][1].is_equal_approx(Point2(20, 212)));
  462. CHECK(r[0][2].is_equal_approx(Point2(47.588089, 122.798492)));
  463. }
  464. SUBCASE("[Geometry2D] Polygon b completely overlaps polygon a") {
  465. b.push_back(Point2(250, 170));
  466. b.push_back(Point2(175, 270));
  467. b.push_back(Point2(10, 210));
  468. b.push_back(Point2(55, 80));
  469. r = Geometry2D::clip_polygons(a, b);
  470. CHECK_MESSAGE(r.is_empty(), "Polygon 'b' completely overlaps polygon 'a'. This should result in no clipped polygons.");
  471. }
  472. SUBCASE("[Geometry2D] Polygon a completely overlaps polygon b") {
  473. b.push_back(Point2(150, 200));
  474. b.push_back(Point2(65, 190));
  475. b.push_back(Point2(80, 140));
  476. r = Geometry2D::clip_polygons(a, b);
  477. REQUIRE_MESSAGE(r.size() == 2, "Polygon 'a' completely overlaps polygon 'b'. This should result in 2 clipped polygons.");
  478. REQUIRE_MESSAGE(r[0].size() == 4, "The resulting clipped polygon should have 4 vertices.");
  479. REQUIRE_MESSAGE(!Geometry2D::is_polygon_clockwise(r[0]), "The resulting clipped polygon (outline) should be counter-clockwise.");
  480. CHECK(r[0][0].is_equal_approx(a[0]));
  481. CHECK(r[0][1].is_equal_approx(a[1]));
  482. CHECK(r[0][2].is_equal_approx(a[2]));
  483. CHECK(r[0][3].is_equal_approx(a[3]));
  484. REQUIRE_MESSAGE(r[1].size() == 3, "The resulting clipped polygon should have 3 vertices.");
  485. REQUIRE_MESSAGE(Geometry2D::is_polygon_clockwise(r[1]), "The resulting clipped polygon (hole) should be clockwise.");
  486. CHECK(r[1][0].is_equal_approx(b[1]));
  487. CHECK(r[1][1].is_equal_approx(b[0]));
  488. CHECK(r[1][2].is_equal_approx(b[2]));
  489. }
  490. }
  491. TEST_CASE("[Geometry2D] Exclude polygons") {
  492. Vector<Point2> a;
  493. Vector<Point2> b;
  494. Vector<Vector<Point2>> r;
  495. a.push_back(Point2(225, 180));
  496. a.push_back(Point2(160, 230));
  497. a.push_back(Point2(20, 212));
  498. a.push_back(Point2(50, 115));
  499. SUBCASE("[Geometry2D] Both polygons are empty") {
  500. r = Geometry2D::exclude_polygons(Vector<Point2>(), Vector<Point2>());
  501. CHECK_MESSAGE(r.is_empty(), "Both polygons are empty. The excluded polygon should also be empty.");
  502. }
  503. SUBCASE("[Geometry2D] One polygon is empty") {
  504. r = Geometry2D::exclude_polygons(a, b);
  505. REQUIRE_MESSAGE(r.size() == 1, "One polygon is non-empty. There should be 1 resulting excluded polygon.");
  506. REQUIRE_MESSAGE(r[0].size() == 4, "The resulting excluded polygon should have 4 vertices.");
  507. CHECK(r[0][0].is_equal_approx(a[0]));
  508. CHECK(r[0][1].is_equal_approx(a[1]));
  509. CHECK(r[0][2].is_equal_approx(a[2]));
  510. CHECK(r[0][3].is_equal_approx(a[3]));
  511. }
  512. SUBCASE("[Geometry2D] Exclude with 2 resulting polygons (outline and hole)") {
  513. b.push_back(Point2(140, 160));
  514. b.push_back(Point2(150, 220));
  515. b.push_back(Point2(40, 200));
  516. b.push_back(Point2(60, 140));
  517. r = Geometry2D::exclude_polygons(a, b);
  518. REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting excluded polygons (outline and hole).");
  519. REQUIRE_MESSAGE(r[0].size() == 4, "The resulting excluded polygon should have 4 vertices.");
  520. REQUIRE_MESSAGE(!Geometry2D::is_polygon_clockwise(r[0]), "The resulting excluded polygon (outline) should be counter-clockwise.");
  521. CHECK(r[0][0].is_equal_approx(a[0]));
  522. CHECK(r[0][1].is_equal_approx(a[1]));
  523. CHECK(r[0][2].is_equal_approx(a[2]));
  524. CHECK(r[0][3].is_equal_approx(a[3]));
  525. REQUIRE_MESSAGE(r[1].size() == 4, "The resulting excluded polygon should have 4 vertices.");
  526. REQUIRE_MESSAGE(Geometry2D::is_polygon_clockwise(r[1]), "The resulting excluded polygon (hole) should be clockwise.");
  527. CHECK(r[1][0].is_equal_approx(Point2(40, 200)));
  528. CHECK(r[1][1].is_equal_approx(Point2(150, 220)));
  529. CHECK(r[1][2].is_equal_approx(Point2(140, 160)));
  530. CHECK(r[1][3].is_equal_approx(Point2(60, 140)));
  531. }
  532. }
  533. TEST_CASE("[Geometry2D] Intersect polyline with polygon") {
  534. Vector<Vector2> l;
  535. Vector<Vector2> p;
  536. Vector<Vector<Point2>> r;
  537. l.push_back(Vector2(100, 90));
  538. l.push_back(Vector2(120, 250));
  539. p.push_back(Vector2(225, 180));
  540. p.push_back(Vector2(160, 230));
  541. p.push_back(Vector2(20, 212));
  542. p.push_back(Vector2(50, 115));
  543. SUBCASE("[Geometry2D] Both line and polygon are empty") {
  544. r = Geometry2D::intersect_polyline_with_polygon(Vector<Vector2>(), Vector<Vector2>());
  545. CHECK_MESSAGE(r.is_empty(), "Both line and polygon are empty. The intersection line should also be empty.");
  546. }
  547. SUBCASE("[Geometry2D] Line is non-empty and polygon is empty") {
  548. r = Geometry2D::intersect_polyline_with_polygon(l, Vector<Vector2>());
  549. CHECK_MESSAGE(r.is_empty(), "The polygon is empty while the line is non-empty. The intersection line should be empty.");
  550. }
  551. SUBCASE("[Geometry2D] Basic intersection with 1 resulting intersection line") {
  552. r = Geometry2D::intersect_polyline_with_polygon(l, p);
  553. REQUIRE_MESSAGE(r.size() == 1, "There should be 1 resulting intersection line.");
  554. REQUIRE_MESSAGE(r[0].size() == 2, "The resulting intersection line should have 2 vertices.");
  555. CHECK(r[0][0].is_equal_approx(Vector2(105.711609, 135.692886)));
  556. CHECK(r[0][1].is_equal_approx(Vector2(116.805809, 224.446457)));
  557. }
  558. SUBCASE("[Geometry2D] Complex intersection with 2 resulting intersection lines") {
  559. l.clear();
  560. l.push_back(Vector2(100, 90));
  561. l.push_back(Vector2(190, 255));
  562. l.push_back(Vector2(135, 260));
  563. l.push_back(Vector2(57, 200));
  564. l.push_back(Vector2(50, 170));
  565. l.push_back(Vector2(15, 155));
  566. r = Geometry2D::intersect_polyline_with_polygon(l, p);
  567. REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting intersection lines.");
  568. REQUIRE_MESSAGE(r[0].size() == 2, "The resulting intersection line should have 2 vertices.");
  569. CHECK(r[0][0].is_equal_approx(Vector2(129.804565, 144.641693)));
  570. CHECK(r[0][1].is_equal_approx(Vector2(171.527084, 221.132996)));
  571. REQUIRE_MESSAGE(r[1].size() == 4, "The resulting intersection line should have 4 vertices.");
  572. CHECK(r[1][0].is_equal_approx(Vector2(83.15609, 220.120087)));
  573. CHECK(r[1][1].is_equal_approx(Vector2(57, 200)));
  574. CHECK(r[1][2].is_equal_approx(Vector2(50, 170)));
  575. CHECK(r[1][3].is_equal_approx(Vector2(34.980492, 163.563065)));
  576. }
  577. }
  578. TEST_CASE("[Geometry2D] Clip polyline with polygon") {
  579. Vector<Vector2> l;
  580. Vector<Vector2> p;
  581. Vector<Vector<Point2>> r;
  582. l.push_back(Vector2(70, 140));
  583. l.push_back(Vector2(160, 320));
  584. p.push_back(Vector2(225, 180));
  585. p.push_back(Vector2(160, 230));
  586. p.push_back(Vector2(20, 212));
  587. p.push_back(Vector2(50, 115));
  588. SUBCASE("[Geometry2D] Both line and polygon are empty") {
  589. r = Geometry2D::clip_polyline_with_polygon(Vector<Vector2>(), Vector<Vector2>());
  590. CHECK_MESSAGE(r.is_empty(), "Both line and polygon are empty. The clipped line should also be empty.");
  591. }
  592. SUBCASE("[Geometry2D] Polygon is empty and line is non-empty") {
  593. r = Geometry2D::clip_polyline_with_polygon(l, Vector<Vector2>());
  594. REQUIRE_MESSAGE(r.size() == 1, "There should be 1 resulting clipped line.");
  595. REQUIRE_MESSAGE(r[0].size() == 2, "The resulting clipped line should have 2 vertices.");
  596. CHECK(r[0][0].is_equal_approx(l[0]));
  597. CHECK(r[0][1].is_equal_approx(l[1]));
  598. }
  599. SUBCASE("[Geometry2D] Basic clip with 1 resulting clipped line") {
  600. r = Geometry2D::clip_polyline_with_polygon(l, p);
  601. REQUIRE_MESSAGE(r.size() == 1, "There should be 1 resulting clipped line.");
  602. REQUIRE_MESSAGE(r[0].size() == 2, "The resulting clipped line should have 2 vertices.");
  603. CHECK(r[0][0].is_equal_approx(Vector2(111.908401, 223.816803)));
  604. CHECK(r[0][1].is_equal_approx(Vector2(160, 320)));
  605. }
  606. SUBCASE("[Geometry2D] Complex clip with 2 resulting clipped lines") {
  607. l.clear();
  608. l.push_back(Vector2(55, 70));
  609. l.push_back(Vector2(50, 190));
  610. l.push_back(Vector2(120, 165));
  611. l.push_back(Vector2(122, 250));
  612. l.push_back(Vector2(160, 320));
  613. r = Geometry2D::clip_polyline_with_polygon(l, p);
  614. REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting clipped lines.");
  615. REQUIRE_MESSAGE(r[0].size() == 3, "The resulting clipped line should have 3 vertices.");
  616. CHECK(r[0][0].is_equal_approx(Vector2(121.412682, 225.038757)));
  617. CHECK(r[0][1].is_equal_approx(Vector2(122, 250)));
  618. CHECK(r[0][2].is_equal_approx(Vector2(160, 320)));
  619. REQUIRE_MESSAGE(r[1].size() == 2, "The resulting clipped line should have 2 vertices.");
  620. CHECK(r[1][0].is_equal_approx(Vector2(55, 70)));
  621. CHECK(r[1][1].is_equal_approx(Vector2(53.07737, 116.143021)));
  622. }
  623. }
  624. TEST_CASE("[Geometry2D] Convex hull") {
  625. Vector<Point2> a;
  626. Vector<Point2> r;
  627. a.push_back(Point2(-4, -8));
  628. a.push_back(Point2(-10, -4));
  629. a.push_back(Point2(8, 2));
  630. a.push_back(Point2(-6, 10));
  631. a.push_back(Point2(-12, 4));
  632. a.push_back(Point2(10, -8));
  633. a.push_back(Point2(4, 8));
  634. SUBCASE("[Geometry2D] No points") {
  635. r = Geometry2D::convex_hull(Vector<Vector2>());
  636. CHECK_MESSAGE(r.is_empty(), "The convex hull should be empty if there are no input points.");
  637. }
  638. SUBCASE("[Geometry2D] Single point") {
  639. Vector<Point2> b;
  640. b.push_back(Point2(4, -3));
  641. r = Geometry2D::convex_hull(b);
  642. REQUIRE_MESSAGE(r.size() == 1, "Convex hull should contain 1 point.");
  643. CHECK(r[0].is_equal_approx(b[0]));
  644. }
  645. SUBCASE("[Geometry2D] All points form the convex hull") {
  646. r = Geometry2D::convex_hull(a);
  647. REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points.");
  648. CHECK(r[0].is_equal_approx(Point2(-12, 4)));
  649. CHECK(r[1].is_equal_approx(Point2(-10, -4)));
  650. CHECK(r[2].is_equal_approx(Point2(-4, -8)));
  651. CHECK(r[3].is_equal_approx(Point2(10, -8)));
  652. CHECK(r[4].is_equal_approx(Point2(8, 2)));
  653. CHECK(r[5].is_equal_approx(Point2(4, 8)));
  654. CHECK(r[6].is_equal_approx(Point2(-6, 10)));
  655. CHECK(r[7].is_equal_approx(Point2(-12, 4)));
  656. }
  657. SUBCASE("[Geometry2D] Add extra points inside original convex hull") {
  658. a.push_back(Point2(-4, -8));
  659. a.push_back(Point2(0, 0));
  660. a.push_back(Point2(0, 8));
  661. a.push_back(Point2(-10, -3));
  662. a.push_back(Point2(9, -4));
  663. a.push_back(Point2(6, 4));
  664. r = Geometry2D::convex_hull(a);
  665. REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points.");
  666. CHECK(r[0].is_equal_approx(Point2(-12, 4)));
  667. CHECK(r[1].is_equal_approx(Point2(-10, -4)));
  668. CHECK(r[2].is_equal_approx(Point2(-4, -8)));
  669. CHECK(r[3].is_equal_approx(Point2(10, -8)));
  670. CHECK(r[4].is_equal_approx(Point2(8, 2)));
  671. CHECK(r[5].is_equal_approx(Point2(4, 8)));
  672. CHECK(r[6].is_equal_approx(Point2(-6, 10)));
  673. CHECK(r[7].is_equal_approx(Point2(-12, 4)));
  674. }
  675. SUBCASE("[Geometry2D] Add extra points on border of original convex hull") {
  676. a.push_back(Point2(9, -3));
  677. a.push_back(Point2(-2, -8));
  678. r = Geometry2D::convex_hull(a);
  679. REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points.");
  680. CHECK(r[0].is_equal_approx(Point2(-12, 4)));
  681. CHECK(r[1].is_equal_approx(Point2(-10, -4)));
  682. CHECK(r[2].is_equal_approx(Point2(-4, -8)));
  683. CHECK(r[3].is_equal_approx(Point2(10, -8)));
  684. CHECK(r[4].is_equal_approx(Point2(8, 2)));
  685. CHECK(r[5].is_equal_approx(Point2(4, 8)));
  686. CHECK(r[6].is_equal_approx(Point2(-6, 10)));
  687. CHECK(r[7].is_equal_approx(Point2(-12, 4)));
  688. }
  689. SUBCASE("[Geometry2D] Add extra points outside border of original convex hull") {
  690. a.push_back(Point2(-11, -1));
  691. a.push_back(Point2(7, 6));
  692. r = Geometry2D::convex_hull(a);
  693. REQUIRE_MESSAGE(r.size() == 10, "Convex hull should contain 10 points.");
  694. CHECK(r[0].is_equal_approx(Point2(-12, 4)));
  695. CHECK(r[1].is_equal_approx(Point2(-11, -1)));
  696. CHECK(r[2].is_equal_approx(Point2(-10, -4)));
  697. CHECK(r[3].is_equal_approx(Point2(-4, -8)));
  698. CHECK(r[4].is_equal_approx(Point2(10, -8)));
  699. CHECK(r[5].is_equal_approx(Point2(8, 2)));
  700. CHECK(r[6].is_equal_approx(Point2(7, 6)));
  701. CHECK(r[7].is_equal_approx(Point2(4, 8)));
  702. CHECK(r[8].is_equal_approx(Point2(-6, 10)));
  703. CHECK(r[9].is_equal_approx(Point2(-12, 4)));
  704. }
  705. }
  706. TEST_CASE("[Geometry2D] Bresenham line") {
  707. Vector<Vector2i> r;
  708. SUBCASE("[Geometry2D] Single point") {
  709. r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(0, 0));
  710. REQUIRE_MESSAGE(r.size() == 1, "The Bresenham line should contain exactly one point.");
  711. CHECK(r[0] == Vector2i(0, 0));
  712. }
  713. SUBCASE("[Geometry2D] Line parallel to x-axis") {
  714. r = Geometry2D::bresenham_line(Point2i(1, 2), Point2i(5, 2));
  715. REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
  716. CHECK(r[0] == Vector2i(1, 2));
  717. CHECK(r[1] == Vector2i(2, 2));
  718. CHECK(r[2] == Vector2i(3, 2));
  719. CHECK(r[3] == Vector2i(4, 2));
  720. CHECK(r[4] == Vector2i(5, 2));
  721. }
  722. SUBCASE("[Geometry2D] 45 degree line from the origin") {
  723. r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 4));
  724. REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
  725. CHECK(r[0] == Vector2i(0, 0));
  726. CHECK(r[1] == Vector2i(1, 1));
  727. CHECK(r[2] == Vector2i(2, 2));
  728. CHECK(r[3] == Vector2i(3, 3));
  729. CHECK(r[4] == Vector2i(4, 4));
  730. }
  731. SUBCASE("[Geometry2D] Sloped line going up one unit") {
  732. r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 1));
  733. REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
  734. CHECK(r[0] == Vector2i(0, 0));
  735. CHECK(r[1] == Vector2i(1, 0));
  736. CHECK(r[2] == Vector2i(2, 0));
  737. CHECK(r[3] == Vector2i(3, 1));
  738. CHECK(r[4] == Vector2i(4, 1));
  739. }
  740. SUBCASE("[Geometry2D] Sloped line going up two units") {
  741. r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 2));
  742. REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
  743. CHECK(r[0] == Vector2i(0, 0));
  744. CHECK(r[1] == Vector2i(1, 0));
  745. CHECK(r[2] == Vector2i(2, 1));
  746. CHECK(r[3] == Vector2i(3, 1));
  747. CHECK(r[4] == Vector2i(4, 2));
  748. }
  749. SUBCASE("[Geometry2D] Long sloped line") {
  750. r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(11, 5));
  751. REQUIRE_MESSAGE(r.size() == 12, "The Bresenham line should contain exactly twelve points.");
  752. CHECK(r[0] == Vector2i(0, 0));
  753. CHECK(r[1] == Vector2i(1, 0));
  754. CHECK(r[2] == Vector2i(2, 1));
  755. CHECK(r[3] == Vector2i(3, 1));
  756. CHECK(r[4] == Vector2i(4, 2));
  757. CHECK(r[5] == Vector2i(5, 2));
  758. CHECK(r[6] == Vector2i(6, 3));
  759. CHECK(r[7] == Vector2i(7, 3));
  760. CHECK(r[8] == Vector2i(8, 4));
  761. CHECK(r[9] == Vector2i(9, 4));
  762. CHECK(r[10] == Vector2i(10, 5));
  763. CHECK(r[11] == Vector2i(11, 5));
  764. }
  765. }
  766. } // namespace TestGeometry2D