test_geometry.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. #include <catch2/catch.hpp>
  2. #include "libslic3r/Point.hpp"
  3. #include "libslic3r/BoundingBox.hpp"
  4. #include "libslic3r/Polygon.hpp"
  5. #include "libslic3r/Polyline.hpp"
  6. #include "libslic3r/Line.hpp"
  7. #include "libslic3r/Geometry.hpp"
  8. #include "libslic3r/Geometry/Circle.hpp"
  9. #include "libslic3r/Geometry/ConvexHull.hpp"
  10. #include "libslic3r/ClipperUtils.hpp"
  11. #include "libslic3r/ShortestPath.hpp"
  12. //#include <random>
  13. //#include "libnest2d/tools/benchmark.h"
  14. #include "libslic3r/SVG.hpp"
  15. #include "../data/prusaparts.hpp"
  16. #include <unordered_set>
  17. using namespace Slic3r;
  18. TEST_CASE("Line::parallel_to", "[Geometry]"){
  19. Line l{ { 100000, 0 }, { 0, 0 } };
  20. Line l2{ { 200000, 0 }, { 0, 0 } };
  21. REQUIRE(l.parallel_to(l));
  22. REQUIRE(l.parallel_to(l2));
  23. Line l3(l2);
  24. l3.rotate(0.9 * EPSILON, { 0, 0 });
  25. REQUIRE(l.parallel_to(l3));
  26. Line l4(l2);
  27. l4.rotate(1.1 * EPSILON, { 0, 0 });
  28. REQUIRE(! l.parallel_to(l4));
  29. // The angle epsilon is so low that vectors shorter than 100um rotated by epsilon radians are not rotated at all.
  30. Line l5{ { 20000, 0 }, { 0, 0 } };
  31. l5.rotate(1.1 * EPSILON, { 0, 0 });
  32. REQUIRE(l.parallel_to(l5));
  33. l.rotate(1., { 0, 0 });
  34. Point offset{ 342876, 97636249 };
  35. l.translate(offset);
  36. l3.rotate(1., { 0, 0 });
  37. l3.translate(offset);
  38. l4.rotate(1., { 0, 0 });
  39. l4.translate(offset);
  40. REQUIRE(l.parallel_to(l3));
  41. REQUIRE(!l.parallel_to(l4));
  42. }
  43. TEST_CASE("Line::perpendicular_to", "[Geometry]") {
  44. Line l{ { 100000, 0 }, { 0, 0 } };
  45. Line l2{ { 0, 200000 }, { 0, 0 } };
  46. REQUIRE(! l.perpendicular_to(l));
  47. REQUIRE(l.perpendicular_to(l2));
  48. Line l3(l2);
  49. l3.rotate(0.9 * EPSILON, { 0, 0 });
  50. REQUIRE(l.perpendicular_to(l3));
  51. Line l4(l2);
  52. l4.rotate(1.1 * EPSILON, { 0, 0 });
  53. REQUIRE(! l.perpendicular_to(l4));
  54. // The angle epsilon is so low that vectors shorter than 100um rotated by epsilon radians are not rotated at all.
  55. Line l5{ { 0, 20000 }, { 0, 0 } };
  56. l5.rotate(1.1 * EPSILON, { 0, 0 });
  57. REQUIRE(l.perpendicular_to(l5));
  58. l.rotate(1., { 0, 0 });
  59. Point offset{ 342876, 97636249 };
  60. l.translate(offset);
  61. l3.rotate(1., { 0, 0 });
  62. l3.translate(offset);
  63. l4.rotate(1., { 0, 0 });
  64. l4.translate(offset);
  65. REQUIRE(l.perpendicular_to(l3));
  66. REQUIRE(! l.perpendicular_to(l4));
  67. }
  68. TEST_CASE("Polygon::contains works properly", "[Geometry]"){
  69. // this test was failing on Windows (GH #1950)
  70. Slic3r::Polygon polygon(Points({
  71. {207802834,-57084522},
  72. {196528149,-37556190},
  73. {173626821,-25420928},
  74. {171285751,-21366123},
  75. {118673592,-21366123},
  76. {116332562,-25420928},
  77. {93431208,-37556191},
  78. {82156517,-57084523},
  79. {129714478,-84542120},
  80. {160244873,-84542120}
  81. }));
  82. Point point(95706562, -57294774);
  83. REQUIRE(polygon.contains(point));
  84. }
  85. SCENARIO("Intersections of line segments", "[Geometry]"){
  86. GIVEN("Integer coordinates"){
  87. Line line1(Point(5,15),Point(30,15));
  88. Line line2(Point(10,20), Point(10,10));
  89. THEN("The intersection is valid"){
  90. Point point;
  91. line1.intersection(line2,&point);
  92. REQUIRE(Point(10,15) == point);
  93. }
  94. }
  95. GIVEN("Scaled coordinates"){
  96. Line line1(Point(73.6310778185108 / 0.00001, 371.74239268924 / 0.00001), Point(73.6310778185108 / 0.00001, 501.74239268924 / 0.00001));
  97. Line line2(Point(75/0.00001, 437.9853/0.00001), Point(62.7484/0.00001, 440.4223/0.00001));
  98. THEN("There is still an intersection"){
  99. Point point;
  100. REQUIRE(line1.intersection(line2,&point));
  101. }
  102. }
  103. }
  104. SCENARIO("polygon_is_convex works") {
  105. GIVEN("A square of dimension 10") {
  106. WHEN("Polygon is convex clockwise") {
  107. Polygon cw_square { { {0, 0}, {0,10}, {10,10}, {10,0} } };
  108. THEN("it is not convex") {
  109. REQUIRE(! polygon_is_convex(cw_square));
  110. }
  111. }
  112. WHEN("Polygon is convex counter-clockwise") {
  113. Polygon ccw_square { { {0, 0}, {10,0}, {10,10}, {0,10} } };
  114. THEN("it is convex") {
  115. REQUIRE(polygon_is_convex(ccw_square));
  116. }
  117. }
  118. }
  119. GIVEN("A concave polygon") {
  120. Polygon concave = { {0,0}, {10,0}, {10,10}, {0,10}, {0,6}, {4,6}, {4,4}, {0,4} };
  121. THEN("It is not convex") {
  122. REQUIRE(! polygon_is_convex(concave));
  123. }
  124. }
  125. }
  126. TEST_CASE("Creating a polyline generates the obvious lines", "[Geometry]"){
  127. Slic3r::Polyline polyline;
  128. polyline.points = Points({Point(0, 0), Point(10, 0), Point(20, 0)});
  129. REQUIRE(polyline.lines().at(0).a == Point(0,0));
  130. REQUIRE(polyline.lines().at(0).b == Point(10,0));
  131. REQUIRE(polyline.lines().at(1).a == Point(10,0));
  132. REQUIRE(polyline.lines().at(1).b == Point(20,0));
  133. }
  134. TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){
  135. Slic3r::Polygon polygon(Points({Point(0, 0), Point(10, 0), Point(5, 5)}));
  136. Slic3r::Polyline split = polygon.split_at_index(1);
  137. REQUIRE(split.points[0]==Point(10,0));
  138. REQUIRE(split.points[1]==Point(5,5));
  139. REQUIRE(split.points[2]==Point(0,0));
  140. REQUIRE(split.points[3]==Point(10,0));
  141. }
  142. SCENARIO("BoundingBox", "[Geometry]") {
  143. WHEN("Bounding boxes are scaled") {
  144. BoundingBox bb(Points({Point(0, 1), Point(10, 2), Point(20, 2)}));
  145. bb.scale(2);
  146. REQUIRE(bb.min == Point(0,2));
  147. REQUIRE(bb.max == Point(40,4));
  148. }
  149. WHEN("BoundingBox constructed from points") {
  150. BoundingBox bb(Points{ {100,200}, {100, 200}, {500, -600} });
  151. THEN("minimum is correct") {
  152. REQUIRE(bb.min == Point{100,-600});
  153. }
  154. THEN("maximum is correct") {
  155. REQUIRE(bb.max == Point{500,200});
  156. }
  157. }
  158. WHEN("BoundingBox constructed from a single point") {
  159. BoundingBox bb;
  160. bb.merge({10, 10});
  161. THEN("minimum equals to the only defined point") {
  162. REQUIRE(bb.min == Point{10,10});
  163. }
  164. THEN("maximum equals to the only defined point") {
  165. REQUIRE(bb.max == Point{10,10});
  166. }
  167. }
  168. }
  169. TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
  170. Slic3r::Polyline tmp = { Point(10,10), Point(20,10) };
  171. Slic3r::Polygon area = offset(tmp,5).at(0);
  172. REQUIRE(area.area() == Slic3r::Polygon(Points({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
  173. }
  174. SCENARIO("Circle Fit, 3 points", "[Geometry]") {
  175. WHEN("Three points make a circle") {
  176. double s1 = scaled<double>(1.);
  177. THEN("circle_center(): A center point { 0, 0 } is returned") {
  178. Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON);
  179. REQUIRE(is_approx(center, Vec2d(0, 0)));
  180. }
  181. THEN("circle_center(): A center point { 0, 0 } is returned for points in reverse") {
  182. Vec2d center = Geometry::circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON);
  183. REQUIRE(is_approx(center, Vec2d(0, 0)));
  184. }
  185. THEN("try_circle_center(): A center point { 0, 0 } is returned") {
  186. std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON);
  187. REQUIRE(center);
  188. REQUIRE(is_approx(*center, Vec2d(0, 0)));
  189. }
  190. THEN("try_circle_center(): A center point { 0, 0 } is returned for points in reverse") {
  191. std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON);
  192. REQUIRE(center);
  193. REQUIRE(is_approx(*center, Vec2d(0, 0)));
  194. }
  195. }
  196. WHEN("Three points are collinear") {
  197. double s1 = scaled<double>(1.);
  198. THEN("circle_center(): A center point { 2, 0 } is returned") {
  199. Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON);
  200. REQUIRE(is_approx(center, Vec2d(2. * s1, 0)));
  201. }
  202. THEN("try_circle_center(): Fails for collinear points") {
  203. std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON);
  204. REQUIRE(! center);
  205. }
  206. }
  207. }
  208. SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
  209. GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
  210. Vec2d expected_center(-6, 0);
  211. Vec2ds sample {Vec2d(6.0, 0), Vec2d(5.1961524, 3), Vec2d(3 ,5.1961524), Vec2d(0, 6.0), Vec2d(3, 5.1961524), Vec2d(-5.1961524, 3), Vec2d(-6.0, 0)};
  212. std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d& a) { return a + expected_center;});
  213. WHEN("Circle fit is called on the entire array") {
  214. Vec2d result_center(0,0);
  215. result_center = Geometry::circle_center_taubin_newton(sample);
  216. THEN("A center point of -6,0 is returned.") {
  217. REQUIRE(is_approx(result_center, expected_center));
  218. }
  219. }
  220. WHEN("Circle fit is called on the first four points") {
  221. Vec2d result_center(0,0);
  222. result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
  223. THEN("A center point of -6,0 is returned.") {
  224. REQUIRE(is_approx(result_center, expected_center));
  225. }
  226. }
  227. WHEN("Circle fit is called on the middle four points") {
  228. Vec2d result_center(0,0);
  229. result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
  230. THEN("A center point of -6,0 is returned.") {
  231. REQUIRE(is_approx(result_center, expected_center));
  232. }
  233. }
  234. }
  235. GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
  236. Vec2d expected_center(-3, 9);
  237. Vec2ds sample {Vec2d(6.0, 0), Vec2d(5.1961524, 3), Vec2d(3 ,5.1961524),
  238. Vec2d(0, 6.0),
  239. Vec2d(3, 5.1961524), Vec2d(-5.1961524, 3), Vec2d(-6.0, 0)};
  240. std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d& a) { return a + expected_center;});
  241. WHEN("Circle fit is called on the entire array") {
  242. Vec2d result_center(0,0);
  243. result_center = Geometry::circle_center_taubin_newton(sample);
  244. THEN("A center point of 3,9 is returned.") {
  245. REQUIRE(is_approx(result_center, expected_center));
  246. }
  247. }
  248. WHEN("Circle fit is called on the first four points") {
  249. Vec2d result_center(0,0);
  250. result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
  251. THEN("A center point of 3,9 is returned.") {
  252. REQUIRE(is_approx(result_center, expected_center));
  253. }
  254. }
  255. WHEN("Circle fit is called on the middle four points") {
  256. Vec2d result_center(0,0);
  257. result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
  258. THEN("A center point of 3,9 is returned.") {
  259. REQUIRE(is_approx(result_center, expected_center));
  260. }
  261. }
  262. }
  263. GIVEN("A vector of Points arranged in a half-circle with approximately the same distance R from some point") {
  264. Point expected_center { Point::new_scale(-3, 9)};
  265. Points sample {Point::new_scale(6.0, 0), Point::new_scale(5.1961524, 3), Point::new_scale(3 ,5.1961524),
  266. Point::new_scale(0, 6.0),
  267. Point::new_scale(3, 5.1961524), Point::new_scale(-5.1961524, 3), Point::new_scale(-6.0, 0)};
  268. std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Point& a) { return a + expected_center;});
  269. WHEN("Circle fit is called on the entire array") {
  270. Point result_center(0,0);
  271. result_center = Geometry::circle_center_taubin_newton(sample);
  272. THEN("A center point of scaled 3,9 is returned.") {
  273. REQUIRE(is_approx(result_center, expected_center));
  274. }
  275. }
  276. WHEN("Circle fit is called on the first four points") {
  277. Point result_center(0,0);
  278. result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
  279. THEN("A center point of scaled 3,9 is returned.") {
  280. REQUIRE(is_approx(result_center, expected_center));
  281. }
  282. }
  283. WHEN("Circle fit is called on the middle four points") {
  284. Point result_center(0,0);
  285. result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
  286. THEN("A center point of scaled 3,9 is returned.") {
  287. REQUIRE(is_approx(result_center, expected_center));
  288. }
  289. }
  290. }
  291. }
  292. SCENARIO("Circle Fit, least squares by decomposition or by solving normal equation", "[Geometry]") {
  293. auto test_circle_fit = [](const Geometry::Circled &circle, const Vec2d &center, const double radius) {
  294. THEN("A center point matches.") {
  295. REQUIRE(is_approx(circle.center, center));
  296. }
  297. THEN("Radius matches") {
  298. REQUIRE(is_approx(circle.radius, radius));
  299. }
  300. };
  301. GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
  302. const Vec2d expected_center(-6., 0.);
  303. const double expected_radius = 6.;
  304. Vec2ds sample{Vec2d(6.0, 0), Vec2d(5.1961524, 3), Vec2d(3 ,5.1961524), Vec2d(0, 6.0), Vec2d(3, 5.1961524), Vec2d(-5.1961524, 3), Vec2d(-6.0, 0)};
  305. std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d &a) { return a + expected_center; });
  306. WHEN("Circle fit is called on the entire array, least squares SVD") {
  307. test_circle_fit(Geometry::circle_linear_least_squares_svd(sample), expected_center, expected_radius);
  308. }
  309. WHEN("Circle fit is called on the first four points, least squares SVD") {
  310. test_circle_fit(Geometry::circle_linear_least_squares_svd(Vec2ds(sample.cbegin(), sample.cbegin() + 4)), expected_center, expected_radius);
  311. }
  312. WHEN("Circle fit is called on the middle four points, least squares SVD") {
  313. test_circle_fit(Geometry::circle_linear_least_squares_svd(Vec2ds(sample.cbegin() + 2, sample.cbegin() + 6)), expected_center, expected_radius);
  314. }
  315. WHEN("Circle fit is called on the entire array, least squares QR decomposition") {
  316. test_circle_fit(Geometry::circle_linear_least_squares_qr(sample), expected_center, expected_radius);
  317. }
  318. WHEN("Circle fit is called on the first four points, least squares QR decomposition") {
  319. test_circle_fit(Geometry::circle_linear_least_squares_qr(Vec2ds(sample.cbegin(), sample.cbegin() + 4)), expected_center, expected_radius);
  320. }
  321. WHEN("Circle fit is called on the middle four points, least squares QR decomposition") {
  322. test_circle_fit(Geometry::circle_linear_least_squares_qr(Vec2ds(sample.cbegin() + 2, sample.cbegin() + 6)), expected_center, expected_radius);
  323. }
  324. WHEN("Circle fit is called on the entire array, least squares by normal equations") {
  325. test_circle_fit(Geometry::circle_linear_least_squares_normal(sample), expected_center, expected_radius);
  326. }
  327. WHEN("Circle fit is called on the first four points, least squares by normal equations") {
  328. test_circle_fit(Geometry::circle_linear_least_squares_normal(Vec2ds(sample.cbegin(), sample.cbegin() + 4)), expected_center, expected_radius);
  329. }
  330. WHEN("Circle fit is called on the middle four points, least squares by normal equations") {
  331. test_circle_fit(Geometry::circle_linear_least_squares_normal(Vec2ds(sample.cbegin() + 2, sample.cbegin() + 6)), expected_center, expected_radius);
  332. }
  333. }
  334. }
  335. TEST_CASE("smallest_enclosing_circle_welzl", "[Geometry]") {
  336. // Some random points in plane.
  337. Points pts {
  338. { 89243, 4359 }, { 763465, 59687 }, { 3245, 734987 }, { 2459867, 987634 }, { 759866, 67843982 }, { 9754687, 9834658 }, { 87235089, 743984373 },
  339. { 65874456, 2987546 }, { 98234524, 657654873 }, { 786243598, 287934765 }, { 824356, 734265 }, { 82576449, 7864534 }, { 7826345, 3984765 }
  340. };
  341. const auto c = Slic3r::Geometry::smallest_enclosing_circle_welzl(pts);
  342. // The radius returned is inflated by SCALED_EPSILON, thus all points should be inside.
  343. bool all_inside = std::all_of(pts.begin(), pts.end(), [c](const Point &pt){ return c.contains(pt.cast<double>()); });
  344. auto c2(c);
  345. c2.radius -= SCALED_EPSILON * 2.1;
  346. auto num_on_boundary = std::count_if(pts.begin(), pts.end(), [c2](const Point& pt) { return ! c2.contains(pt.cast<double>(), SCALED_EPSILON); });
  347. REQUIRE(all_inside);
  348. REQUIRE(num_on_boundary == 3);
  349. }
  350. SCENARIO("Path chaining", "[Geometry]") {
  351. GIVEN("A path") {
  352. Points points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) };
  353. THEN("Chained with no diagonals (thus 26 units long)") {
  354. // if chain_points() works correctly, these points should be joined with no diagonal paths
  355. std::vector<Points::size_type> indices = chain_points(points);
  356. for (Points::size_type i = 0; i + 1 < indices.size(); ++ i) {
  357. double dist = (points.at(indices.at(i)).cast<double>() - points.at(indices.at(i+1)).cast<double>()).norm();
  358. REQUIRE(std::abs(dist-26) <= EPSILON);
  359. }
  360. }
  361. }
  362. GIVEN("Gyroid infill end points") {
  363. Polylines polylines = {
  364. { {28122608, 3221037}, {27919139, 56036027} },
  365. { {33642863, 3400772}, {30875220, 56450360} },
  366. { {34579315, 3599827}, {35049758, 55971572} },
  367. { {26483070, 3374004}, {23971830, 55763598} },
  368. { {38931405, 4678879}, {38740053, 55077714} },
  369. { {20311895, 5015778}, {20079051, 54551952} },
  370. { {16463068, 6773342}, {18823514, 53992958} },
  371. { {44433771, 7424951}, {42629462, 53346059} },
  372. { {15697614, 7329492}, {15350896, 52089991} },
  373. { {48085792, 10147132}, {46435427, 50792118} },
  374. { {48828819, 10972330}, {49126582, 48368374} },
  375. { {9654526, 12656711}, {10264020, 47691584} },
  376. { {5726905, 18648632}, {8070762, 45082416} },
  377. { {54818187, 39579970}, {52974912, 43271272} },
  378. { {4464342, 37371742}, {5027890, 39106220} },
  379. { {54139746, 18417661}, {55177987, 38472580} },
  380. { {56527590, 32058461}, {56316456, 34067185} },
  381. { {3303988, 29215290}, {3569863, 32985633} },
  382. { {56255666, 25025857}, {56478310, 27144087} },
  383. { {4300034, 22805361}, {3667946, 25752601} },
  384. { {8266122, 14250611}, {6244813, 17751595} },
  385. { {12177955, 9886741}, {10703348, 11491900} }
  386. };
  387. Polylines chained = chain_polylines(polylines);
  388. THEN("Chained taking the shortest path") {
  389. double connection_length = 0.;
  390. for (size_t i = 1; i < chained.size(); ++i) {
  391. const Polyline &pl1 = chained[i - 1];
  392. const Polyline &pl2 = chained[i];
  393. connection_length += (pl2.first_point() - pl1.last_point()).cast<double>().norm();
  394. }
  395. REQUIRE(connection_length < 85206000.);
  396. }
  397. }
  398. GIVEN("Loop pieces") {
  399. Point a { 2185796, 19058485 };
  400. Point b { 3957902, 18149382 };
  401. Point c { 2912841, 18790564 };
  402. Point d { 2831848, 18832390 };
  403. Point e { 3179601, 18627769 };
  404. Point f { 3137952, 18653370 };
  405. Polylines polylines = { { a, b },
  406. { c, d },
  407. { e, f },
  408. { d, a },
  409. { f, c },
  410. { b, e } };
  411. Polylines chained = chain_polylines(polylines, &a);
  412. THEN("Connected without a gap") {
  413. for (size_t i = 0; i < chained.size(); ++i) {
  414. const Polyline &pl1 = (i == 0) ? chained.back() : chained[i - 1];
  415. const Polyline &pl2 = chained[i];
  416. REQUIRE(pl1.points.back() == pl2.points.front());
  417. }
  418. }
  419. }
  420. }
  421. SCENARIO("Line distances", "[Geometry]"){
  422. GIVEN("A line"){
  423. Line line(Point(0, 0), Point(20, 0));
  424. THEN("Points on the line segment have 0 distance"){
  425. REQUIRE(line.distance_to(Point(0, 0)) == 0);
  426. REQUIRE(line.distance_to(Point(20, 0)) == 0);
  427. REQUIRE(line.distance_to(Point(10, 0)) == 0);
  428. }
  429. THEN("Points off the line have the appropriate distance"){
  430. REQUIRE(line.distance_to(Point(10, 10)) == 10);
  431. REQUIRE(line.distance_to(Point(50, 0)) == 30);
  432. }
  433. }
  434. }
  435. SCENARIO("Calculating angles", "[Geometry]")
  436. {
  437. GIVEN(("Vectors 30 degrees apart"))
  438. {
  439. std::vector<std::pair<Point, Point>> pts {
  440. { {1000, 0}, { 866, 500 } },
  441. { { 866, 500 }, { 500, 866 } },
  442. { { 500, 866 }, { 0, 1000 } },
  443. { { -500, 866 }, { -866, 500 } }
  444. };
  445. THEN("Angle detected is 30 degrees")
  446. {
  447. for (auto &p : pts)
  448. REQUIRE(is_approx(angle(p.first, p.second), M_PI / 6.));
  449. }
  450. }
  451. GIVEN(("Vectors 30 degrees apart"))
  452. {
  453. std::vector<std::pair<Point, Point>> pts {
  454. { { 866, 500 }, {1000, 0} },
  455. { { 500, 866 }, { 866, 500 } },
  456. { { 0, 1000 }, { 500, 866 } },
  457. { { -866, 500 }, { -500, 866 } }
  458. };
  459. THEN("Angle detected is -30 degrees")
  460. {
  461. for (auto &p : pts)
  462. REQUIRE(is_approx(angle(p.first, p.second), - M_PI / 6.));
  463. }
  464. }
  465. }
  466. SCENARIO("Polygon convex/concave detection", "[Geometry]"){
  467. static constexpr const double angle_threshold = M_PI / 3.;
  468. GIVEN(("A Square with dimension 100")){
  469. auto square = Slic3r::Polygon /*new_scale*/(Points({
  470. Point(100,100),
  471. Point(200,100),
  472. Point(200,200),
  473. Point(100,200)}));
  474. THEN("It has 4 convex points counterclockwise"){
  475. REQUIRE(square.concave_points(angle_threshold).size() == 0);
  476. REQUIRE(square.convex_points(angle_threshold).size() == 4);
  477. }
  478. THEN("It has 4 concave points clockwise"){
  479. square.make_clockwise();
  480. REQUIRE(square.concave_points(angle_threshold).size() == 4);
  481. REQUIRE(square.convex_points(angle_threshold).size() == 0);
  482. }
  483. }
  484. GIVEN("A Square with an extra colinearvertex"){
  485. auto square = Slic3r::Polygon /*new_scale*/(Points({
  486. Point(150,100),
  487. Point(200,100),
  488. Point(200,200),
  489. Point(100,200),
  490. Point(100,100)}));
  491. THEN("It has 4 convex points counterclockwise"){
  492. REQUIRE(square.concave_points(angle_threshold).size() == 0);
  493. REQUIRE(square.convex_points(angle_threshold).size() == 4);
  494. }
  495. }
  496. GIVEN("A Square with an extra collinear vertex in different order"){
  497. auto square = Slic3r::Polygon /*new_scale*/(Points({
  498. Point(200,200),
  499. Point(100,200),
  500. Point(100,100),
  501. Point(150,100),
  502. Point(200,100)}));
  503. THEN("It has 4 convex points counterclockwise"){
  504. REQUIRE(square.concave_points(angle_threshold).size() == 0);
  505. REQUIRE(square.convex_points(angle_threshold).size() == 4);
  506. }
  507. }
  508. GIVEN("A triangle"){
  509. auto triangle = Slic3r::Polygon(Points({
  510. Point(16000170,26257364),
  511. Point(714223,461012),
  512. Point(31286371,461008)
  513. }));
  514. THEN("it has three convex vertices"){
  515. REQUIRE(triangle.concave_points(angle_threshold).size() == 0);
  516. REQUIRE(triangle.convex_points(angle_threshold).size() == 3);
  517. }
  518. }
  519. GIVEN("A triangle with an extra collinear point"){
  520. auto triangle = Slic3r::Polygon(Points({
  521. Point(16000170,26257364),
  522. Point(714223,461012),
  523. Point(20000000,461012),
  524. Point(31286371,461012)
  525. }));
  526. THEN("it has three convex vertices"){
  527. REQUIRE(triangle.concave_points(angle_threshold).size() == 0);
  528. REQUIRE(triangle.convex_points(angle_threshold).size() == 3);
  529. }
  530. }
  531. GIVEN("A polygon with concave vertices with angles of specifically 4/3pi"){
  532. // Two concave vertices of this polygon have angle = PI*4/3, so this test fails
  533. // if epsilon is not used.
  534. auto polygon = Slic3r::Polygon(Points({
  535. Point(60246458,14802768),Point(64477191,12360001),
  536. Point(63727343,11060995),Point(64086449,10853608),
  537. Point(66393722,14850069),Point(66034704,15057334),
  538. Point(65284646,13758387),Point(61053864,16200839),
  539. Point(69200258,30310849),Point(62172547,42483120),
  540. Point(61137680,41850279),Point(67799985,30310848),
  541. Point(51399866,1905506),Point(38092663,1905506),
  542. Point(38092663,692699),Point(52100125,692699)
  543. }));
  544. THEN("the correct number of points are detected"){
  545. REQUIRE(polygon.concave_points(angle_threshold).size() == 6);
  546. REQUIRE(polygon.convex_points(angle_threshold).size() == 10);
  547. }
  548. }
  549. }
  550. TEST_CASE("Triangle Simplification does not result in less than 3 points", "[Geometry]"){
  551. auto triangle = Slic3r::Polygon(Points({
  552. Point(16000170,26257364), Point(714223,461012), Point(31286371,461008)
  553. }));
  554. REQUIRE(triangle.simplify(250000).at(0).points.size() == 3);
  555. }
  556. SCENARIO("Ported from xs/t/14_geometry.t", "[Geometry]"){
  557. GIVEN(("square")){
  558. Slic3r::Points points { { 100, 100 }, {100, 200 }, { 200, 200 }, { 200, 100 }, { 150, 150 } };
  559. Slic3r::Polygon hull = Slic3r::Geometry::convex_hull(points);
  560. SECTION("convex hull returns the correct number of points") { REQUIRE(hull.points.size() == 4); }
  561. }
  562. SECTION("arrange returns expected number of positions") {
  563. Pointfs positions;
  564. Slic3r::Geometry::arrange(4, Vec2d(20, 20), 5, nullptr, positions);
  565. REQUIRE(positions.size() == 4);
  566. }
  567. SECTION("directions_parallel") {
  568. REQUIRE(Slic3r::Geometry::directions_parallel(0, 0, 0));
  569. REQUIRE(Slic3r::Geometry::directions_parallel(0, M_PI, 0));
  570. REQUIRE(Slic3r::Geometry::directions_parallel(0, 0, M_PI / 180));
  571. REQUIRE(Slic3r::Geometry::directions_parallel(0, M_PI, M_PI / 180));
  572. REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, M_PI, 0));
  573. REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, PI, M_PI /180));
  574. }
  575. }
  576. TEST_CASE("Convex polygon intersection on two disjoint squares", "[Geometry][Rotcalip]") {
  577. Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
  578. A.scale(1. / SCALING_FACTOR);
  579. Polygon B = A;
  580. B.translate(20 / SCALING_FACTOR, 0);
  581. bool is_inters = Geometry::convex_polygons_intersect(A, B);
  582. REQUIRE(is_inters == false);
  583. }
  584. TEST_CASE("Convex polygon intersection on two intersecting squares", "[Geometry][Rotcalip]") {
  585. Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
  586. A.scale(1. / SCALING_FACTOR);
  587. Polygon B = A;
  588. B.translate(5 / SCALING_FACTOR, 5 / SCALING_FACTOR);
  589. bool is_inters = Geometry::convex_polygons_intersect(A, B);
  590. REQUIRE(is_inters == true);
  591. }
  592. TEST_CASE("Convex polygon intersection on two squares touching one edge", "[Geometry][Rotcalip]") {
  593. Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
  594. A.scale(1. / SCALING_FACTOR);
  595. Polygon B = A;
  596. B.translate(10 / SCALING_FACTOR, 0);
  597. bool is_inters = Geometry::convex_polygons_intersect(A, B);
  598. REQUIRE(is_inters == false);
  599. }
  600. TEST_CASE("Convex polygon intersection on two squares touching one vertex", "[Geometry][Rotcalip]") {
  601. Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
  602. A.scale(1. / SCALING_FACTOR);
  603. Polygon B = A;
  604. B.translate(10 / SCALING_FACTOR, 10 / SCALING_FACTOR);
  605. SVG svg{std::string("one_vertex_touch") + ".svg"};
  606. svg.draw(A, "blue");
  607. svg.draw(B, "green");
  608. svg.Close();
  609. bool is_inters = Geometry::convex_polygons_intersect(A, B);
  610. REQUIRE(is_inters == false);
  611. }
  612. TEST_CASE("Convex polygon intersection on two overlapping squares", "[Geometry][Rotcalip]") {
  613. Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
  614. A.scale(1. / SCALING_FACTOR);
  615. Polygon B = A;
  616. bool is_inters = Geometry::convex_polygons_intersect(A, B);
  617. REQUIRE(is_inters == true);
  618. }
  619. //// Only for benchmarking
  620. //static Polygon gen_convex_poly(std::mt19937_64 &rg, size_t point_cnt)
  621. //{
  622. // std::uniform_int_distribution<coord_t> dist(0, 100);
  623. // Polygon out;
  624. // out.points.reserve(point_cnt);
  625. // coord_t tr = dist(rg) * 2 / SCALING_FACTOR;
  626. // for (size_t i = 0; i < point_cnt; ++i)
  627. // out.points.emplace_back(tr + dist(rg) / SCALING_FACTOR,
  628. // tr + dist(rg) / SCALING_FACTOR);
  629. // return Geometry::convex_hull(out.points);
  630. //}
  631. //TEST_CASE("Convex polygon intersection test on random polygons", "[Geometry]") {
  632. // constexpr size_t TEST_CNT = 1000;
  633. // constexpr size_t POINT_CNT = 1000;
  634. // auto seed = std::random_device{}();
  635. //// unsigned long seed = 2525634386;
  636. // std::mt19937_64 rg{seed};
  637. // Benchmark bench;
  638. // auto tests = reserve_vector<std::pair<Polygon, Polygon>>(TEST_CNT);
  639. // auto results = reserve_vector<bool>(TEST_CNT);
  640. // auto expects = reserve_vector<bool>(TEST_CNT);
  641. // for (size_t i = 0; i < TEST_CNT; ++i) {
  642. // tests.emplace_back(gen_convex_poly(rg, POINT_CNT), gen_convex_poly(rg, POINT_CNT));
  643. // }
  644. // bench.start();
  645. // for (const auto &test : tests)
  646. // results.emplace_back(Geometry::convex_polygons_intersect(test.first, test.second));
  647. // bench.stop();
  648. // std::cout << "Test time: " << bench.getElapsedSec() << std::endl;
  649. // bench.start();
  650. // for (const auto &test : tests)
  651. // expects.emplace_back(!intersection(test.first, test.second).empty());
  652. // bench.stop();
  653. // std::cout << "Clipper time: " << bench.getElapsedSec() << std::endl;
  654. // REQUIRE(results.size() == expects.size());
  655. // auto seedstr = std::to_string(seed);
  656. // for (size_t i = 0; i < results.size(); ++i) {
  657. // // std::cout << expects[i] << " ";
  658. // if (results[i] != expects[i]) {
  659. // SVG svg{std::string("fail_seed") + seedstr + "_" + std::to_string(i) + ".svg"};
  660. // svg.draw(tests[i].first, "blue");
  661. // svg.draw(tests[i].second, "green");
  662. // svg.Close();
  663. // // std::cout << std::endl;
  664. // }
  665. // REQUIRE(results[i] == expects[i]);
  666. // }
  667. // std::cout << std::endl;
  668. //}
  669. struct Pair
  670. {
  671. size_t first, second;
  672. bool operator==(const Pair &b) const { return first == b.first && second == b.second; }
  673. };
  674. template<> struct std::hash<Pair> {
  675. size_t operator()(const Pair &c) const
  676. {
  677. return c.first * PRUSA_PART_POLYGONS.size() + c.second;
  678. }
  679. };
  680. TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcalip]") {
  681. // Overlap of the same polygon should always be an intersection
  682. for (size_t i = 0; i < PRUSA_PART_POLYGONS.size(); ++i) {
  683. Polygon P = PRUSA_PART_POLYGONS[i];
  684. P = Geometry::convex_hull(P.points);
  685. bool res = Geometry::convex_polygons_intersect(P, P);
  686. if (!res) {
  687. SVG svg{std::string("fail_self") + std::to_string(i) + ".svg"};
  688. svg.draw(P, "green");
  689. svg.Close();
  690. }
  691. REQUIRE(res == true);
  692. }
  693. std::unordered_set<Pair> combos;
  694. for (size_t i = 0; i < PRUSA_PART_POLYGONS.size(); ++i) {
  695. for (size_t j = 0; j < PRUSA_PART_POLYGONS.size(); ++j) {
  696. if (i != j) {
  697. size_t a = std::min(i, j), b = std::max(i, j);
  698. combos.insert(Pair{a, b});
  699. }
  700. }
  701. }
  702. // All disjoint
  703. for (const auto &combo : combos) {
  704. Polygon A = PRUSA_PART_POLYGONS[combo.first], B = PRUSA_PART_POLYGONS[combo.second];
  705. A = Geometry::convex_hull(A.points);
  706. B = Geometry::convex_hull(B.points);
  707. auto bba = A.bounding_box();
  708. auto bbb = B.bounding_box();
  709. A.translate(-bba.center());
  710. B.translate(-bbb.center());
  711. B.translate(bba.size() + bbb.size());
  712. bool res = Geometry::convex_polygons_intersect(A, B);
  713. bool ref = !intersection(A, B).empty();
  714. if (res != ref) {
  715. SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
  716. svg.draw(A, "blue");
  717. svg.draw(B, "green");
  718. svg.Close();
  719. }
  720. REQUIRE(res == ref);
  721. }
  722. // All intersecting
  723. for (const auto &combo : combos) {
  724. Polygon A = PRUSA_PART_POLYGONS[combo.first], B = PRUSA_PART_POLYGONS[combo.second];
  725. A = Geometry::convex_hull(A.points);
  726. B = Geometry::convex_hull(B.points);
  727. auto bba = A.bounding_box();
  728. auto bbb = B.bounding_box();
  729. A.translate(-bba.center());
  730. B.translate(-bbb.center());
  731. bool res = Geometry::convex_polygons_intersect(A, B);
  732. bool ref = !intersection(A, B).empty();
  733. if (res != ref) {
  734. SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
  735. svg.draw(A, "blue");
  736. svg.draw(B, "green");
  737. svg.Close();
  738. }
  739. REQUIRE(res == ref);
  740. }
  741. }
  742. TEST_CASE("Euler angles roundtrip", "[Geometry]") {
  743. std::vector<Vec3d> euler_angles_vec = {{M_PI/2., -M_PI, 0.},
  744. {M_PI, -M_PI, 0.},
  745. {M_PI, -M_PI, 2*M_PI},
  746. {0., 0., M_PI},
  747. {M_PI, M_PI/2., 0.},
  748. {0.2, 0.3, -0.5}};
  749. // Also include all combinations of zero and +-pi/2:
  750. for (double x : {0., M_PI/2., -M_PI/2.})
  751. for (double y : {0., M_PI/2., -M_PI/2.})
  752. for (double z : {0., M_PI/2., -M_PI/2.})
  753. euler_angles_vec.emplace_back(x, y, z);
  754. for (Vec3d& euler_angles : euler_angles_vec) {
  755. Transform3d trafo1 = Geometry::rotation_transform(euler_angles);
  756. euler_angles = Geometry::extract_rotation(trafo1);
  757. Transform3d trafo2 = Geometry::rotation_transform(euler_angles);
  758. REQUIRE(trafo1.isApprox(trafo2));
  759. }
  760. }