test_geometry.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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/ClipperUtils.hpp"
  9. #include "libslic3r/ShortestPath.hpp"
  10. using namespace Slic3r;
  11. TEST_CASE("Polygon::contains works properly", "[Geometry]"){
  12. // this test was failing on Windows (GH #1950)
  13. Slic3r::Polygon polygon(std::vector<Point>({
  14. Point(207802834,-57084522),
  15. Point(196528149,-37556190),
  16. Point(173626821,-25420928),
  17. Point(171285751,-21366123),
  18. Point(118673592,-21366123),
  19. Point(116332562,-25420928),
  20. Point(93431208,-37556191),
  21. Point(82156517,-57084523),
  22. Point(129714478,-84542120),
  23. Point(160244873,-84542120)
  24. }));
  25. Point point(95706562, -57294774);
  26. REQUIRE(polygon.contains(point));
  27. }
  28. SCENARIO("Intersections of line segments", "[Geometry]"){
  29. GIVEN("Integer coordinates"){
  30. Line line1(Point(5,15),Point(30,15));
  31. Line line2(Point(10,20), Point(10,10));
  32. THEN("The intersection is valid"){
  33. Point point;
  34. line1.intersection(line2,&point);
  35. REQUIRE(Point(10,15) == point);
  36. }
  37. }
  38. GIVEN("Scaled coordinates"){
  39. Line line1(Point(73.6310778185108 / 0.00001, 371.74239268924 / 0.00001), Point(73.6310778185108 / 0.00001, 501.74239268924 / 0.00001));
  40. Line line2(Point(75/0.00001, 437.9853/0.00001), Point(62.7484/0.00001, 440.4223/0.00001));
  41. THEN("There is still an intersection"){
  42. Point point;
  43. REQUIRE(line1.intersection(line2,&point));
  44. }
  45. }
  46. }
  47. /*
  48. Tests for unused methods still written in perl
  49. {
  50. my $polygon = Slic3r::Polygon->new(
  51. [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800],
  52. [43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600],
  53. [75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500],
  54. [107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300],
  55. [82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500],
  56. [285273900, 461246400], [254081000, 515273900],
  57. );
  58. # this points belongs to $polyline
  59. # note: it's actually a vertex, while we should better check an intermediate point
  60. my $point = Slic3r::Point->new(104577600, 327748400);
  61. local $Slic3r::Geometry::epsilon = 1E-5;
  62. is_deeply Slic3r::Geometry::polygon_segment_having_point($polygon, $point)->pp,
  63. [ [107014700, 340000000], [104577600, 327748400] ],
  64. 'polygon_segment_having_point';
  65. }
  66. {
  67. auto point = Point(736310778.185108, 5017423926.8924);
  68. auto line = Line(Point((long int) 627484000, (long int) 3695776000), Point((long int) 750000000, (long int)3720147000));
  69. //is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment';
  70. }
  71. // Possible to delete
  72. {
  73. //my $p1 = [10, 10];
  74. //my $p2 = [10, 20];
  75. //my $p3 = [10, 30];
  76. //my $p4 = [20, 20];
  77. //my $p5 = [0, 20];
  78. THEN("Points in a line give the correct angles"){
  79. //is Slic3r::Geometry::angle3points($p2, $p3, $p1), PI(), 'angle3points';
  80. //is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points';
  81. }
  82. THEN("Left turns give the correct angle"){
  83. //is Slic3r::Geometry::angle3points($p2, $p4, $p3), PI()/2, 'angle3points';
  84. //is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2, 'angle3points';
  85. }
  86. THEN("Right turns give the correct angle"){
  87. //is Slic3r::Geometry::angle3points($p2, $p3, $p4), PI()/2*3, 'angle3points';
  88. //is Slic3r::Geometry::angle3points($p2, $p1, $p5), PI()/2*3, 'angle3points';
  89. }
  90. //my $p1 = [30, 30];
  91. //my $p2 = [20, 20];
  92. //my $p3 = [10, 10];
  93. //my $p4 = [30, 10];
  94. //is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points';
  95. //is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2*3, 'angle3points';
  96. //is Slic3r::Geometry::angle3points($p2, $p1, $p1), 2*PI(), 'angle3points';
  97. }
  98. SCENARIO("polygon_is_convex works"){
  99. GIVEN("A square of dimension 10"){
  100. //my $cw_square = [ [0,0], [0,10], [10,10], [10,0] ];
  101. THEN("It is not convex clockwise"){
  102. //is polygon_is_convex($cw_square), 0, 'cw square is not convex';
  103. }
  104. THEN("It is convex counter-clockwise"){
  105. //is polygon_is_convex([ reverse @$cw_square ]), 1, 'ccw square is convex';
  106. }
  107. }
  108. GIVEN("A concave polygon"){
  109. //my $convex1 = [ [0,0], [10,0], [10,10], [0,10], [0,6], [4,6], [4,4], [0,4] ];
  110. THEN("It is concave"){
  111. //is polygon_is_convex($convex1), 0, 'concave polygon';
  112. }
  113. }
  114. }*/
  115. TEST_CASE("Creating a polyline generates the obvious lines", "[Geometry]"){
  116. Slic3r::Polyline polyline;
  117. polyline.points = std::vector<Point>({Point(0, 0), Point(10, 0), Point(20, 0)});
  118. REQUIRE(polyline.lines().at(0).a == Point(0,0));
  119. REQUIRE(polyline.lines().at(0).b == Point(10,0));
  120. REQUIRE(polyline.lines().at(1).a == Point(10,0));
  121. REQUIRE(polyline.lines().at(1).b == Point(20,0));
  122. }
  123. TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){
  124. Slic3r::Polygon polygon(std::vector<Point>({Point(0, 0), Point(10, 0), Point(5, 5)}));
  125. Slic3r::Polyline split = polygon.split_at_index(1);
  126. REQUIRE(split.points[0]==Point(10,0));
  127. REQUIRE(split.points[1]==Point(5,5));
  128. REQUIRE(split.points[2]==Point(0,0));
  129. REQUIRE(split.points[3]==Point(10,0));
  130. }
  131. TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){
  132. BoundingBox bb(std::vector<Point>({Point(0, 1), Point(10, 2), Point(20, 2)}));
  133. bb.scale(2);
  134. REQUIRE(bb.min == Point(0,2));
  135. REQUIRE(bb.max == Point(40,4));
  136. }
  137. TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
  138. Slic3r::Polyline tmp = { Point(10,10), Point(20,10) };
  139. Slic3r::Polygon area = offset(tmp,5).at(0);
  140. REQUIRE(area.area() == Slic3r::Polygon(std::vector<Point>({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
  141. }
  142. SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
  143. GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
  144. Vec2d expected_center(-6, 0);
  145. 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)};
  146. std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d& a) { return a + expected_center;});
  147. WHEN("Circle fit is called on the entire array") {
  148. Vec2d result_center(0,0);
  149. result_center = Geometry::circle_center_taubin_newton(sample);
  150. THEN("A center point of -6,0 is returned.") {
  151. REQUIRE(is_approx(result_center, expected_center));
  152. }
  153. }
  154. WHEN("Circle fit is called on the first four points") {
  155. Vec2d result_center(0,0);
  156. result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
  157. THEN("A center point of -6,0 is returned.") {
  158. REQUIRE(is_approx(result_center, expected_center));
  159. }
  160. }
  161. WHEN("Circle fit is called on the middle four points") {
  162. Vec2d result_center(0,0);
  163. result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
  164. THEN("A center point of -6,0 is returned.") {
  165. REQUIRE(is_approx(result_center, expected_center));
  166. }
  167. }
  168. }
  169. GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
  170. Vec2d expected_center(-3, 9);
  171. Vec2ds sample {Vec2d(6.0, 0), Vec2d(5.1961524, 3), Vec2d(3 ,5.1961524),
  172. Vec2d(0, 6.0),
  173. Vec2d(3, 5.1961524), Vec2d(-5.1961524, 3), Vec2d(-6.0, 0)};
  174. std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d& a) { return a + expected_center;});
  175. WHEN("Circle fit is called on the entire array") {
  176. Vec2d result_center(0,0);
  177. result_center = Geometry::circle_center_taubin_newton(sample);
  178. THEN("A center point of 3,9 is returned.") {
  179. REQUIRE(is_approx(result_center, expected_center));
  180. }
  181. }
  182. WHEN("Circle fit is called on the first four points") {
  183. Vec2d result_center(0,0);
  184. result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
  185. THEN("A center point of 3,9 is returned.") {
  186. REQUIRE(is_approx(result_center, expected_center));
  187. }
  188. }
  189. WHEN("Circle fit is called on the middle four points") {
  190. Vec2d result_center(0,0);
  191. result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
  192. THEN("A center point of 3,9 is returned.") {
  193. REQUIRE(is_approx(result_center, expected_center));
  194. }
  195. }
  196. }
  197. GIVEN("A vector of Points arranged in a half-circle with approximately the same distance R from some point") {
  198. Point expected_center { Point::new_scale(-3, 9)};
  199. Points sample {Point::new_scale(6.0, 0), Point::new_scale(5.1961524, 3), Point::new_scale(3 ,5.1961524),
  200. Point::new_scale(0, 6.0),
  201. Point::new_scale(3, 5.1961524), Point::new_scale(-5.1961524, 3), Point::new_scale(-6.0, 0)};
  202. std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Point& a) { return a + expected_center;});
  203. WHEN("Circle fit is called on the entire array") {
  204. Point result_center(0,0);
  205. result_center = Geometry::circle_center_taubin_newton(sample);
  206. THEN("A center point of scaled 3,9 is returned.") {
  207. REQUIRE(is_approx(result_center, expected_center));
  208. }
  209. }
  210. WHEN("Circle fit is called on the first four points") {
  211. Point result_center(0,0);
  212. result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
  213. THEN("A center point of scaled 3,9 is returned.") {
  214. REQUIRE(is_approx(result_center, expected_center));
  215. }
  216. }
  217. WHEN("Circle fit is called on the middle four points") {
  218. Point result_center(0,0);
  219. result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
  220. THEN("A center point of scaled 3,9 is returned.") {
  221. REQUIRE(is_approx(result_center, expected_center));
  222. }
  223. }
  224. }
  225. }
  226. SCENARIO("Path chaining", "[Geometry]") {
  227. GIVEN("A path") {
  228. std::vector<Point> 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) };
  229. THEN("Chained with no diagonals (thus 26 units long)") {
  230. std::vector<Points::size_type> indices = chain_points(points);
  231. for (Points::size_type i = 0; i + 1 < indices.size(); ++ i) {
  232. double dist = (points.at(indices.at(i)).cast<double>() - points.at(indices.at(i+1)).cast<double>()).norm();
  233. REQUIRE(std::abs(dist-26) <= EPSILON);
  234. }
  235. }
  236. }
  237. GIVEN("Gyroid infill end points") {
  238. Polylines polylines = {
  239. { {28122608, 3221037}, {27919139, 56036027} },
  240. { {33642863, 3400772}, {30875220, 56450360} },
  241. { {34579315, 3599827}, {35049758, 55971572} },
  242. { {26483070, 3374004}, {23971830, 55763598} },
  243. { {38931405, 4678879}, {38740053, 55077714} },
  244. { {20311895, 5015778}, {20079051, 54551952} },
  245. { {16463068, 6773342}, {18823514, 53992958} },
  246. { {44433771, 7424951}, {42629462, 53346059} },
  247. { {15697614, 7329492}, {15350896, 52089991} },
  248. { {48085792, 10147132}, {46435427, 50792118} },
  249. { {48828819, 10972330}, {49126582, 48368374} },
  250. { {9654526, 12656711}, {10264020, 47691584} },
  251. { {5726905, 18648632}, {8070762, 45082416} },
  252. { {54818187, 39579970}, {52974912, 43271272} },
  253. { {4464342, 37371742}, {5027890, 39106220} },
  254. { {54139746, 18417661}, {55177987, 38472580} },
  255. { {56527590, 32058461}, {56316456, 34067185} },
  256. { {3303988, 29215290}, {3569863, 32985633} },
  257. { {56255666, 25025857}, {56478310, 27144087} },
  258. { {4300034, 22805361}, {3667946, 25752601} },
  259. { {8266122, 14250611}, {6244813, 17751595} },
  260. { {12177955, 9886741}, {10703348, 11491900} }
  261. };
  262. Polylines chained = chain_polylines(polylines);
  263. THEN("Chained taking the shortest path") {
  264. double connection_length = 0.;
  265. for (size_t i = 1; i < chained.size(); ++i) {
  266. const Polyline &pl1 = chained[i - 1];
  267. const Polyline &pl2 = chained[i];
  268. connection_length += (pl2.first_point() - pl1.last_point()).cast<double>().norm();
  269. }
  270. REQUIRE(connection_length < 85206000.);
  271. }
  272. }
  273. GIVEN("Loop pieces") {
  274. Point a { 2185796, 19058485 };
  275. Point b { 3957902, 18149382 };
  276. Point c { 2912841, 18790564 };
  277. Point d { 2831848, 18832390 };
  278. Point e { 3179601, 18627769 };
  279. Point f { 3137952, 18653370 };
  280. Polylines polylines = { { a, b },
  281. { c, d },
  282. { e, f },
  283. { d, a },
  284. { f, c },
  285. { b, e } };
  286. Polylines chained = chain_polylines(polylines, &a);
  287. THEN("Connected without a gap") {
  288. for (size_t i = 0; i < chained.size(); ++i) {
  289. const Polyline &pl1 = (i == 0) ? chained.back() : chained[i - 1];
  290. const Polyline &pl2 = chained[i];
  291. REQUIRE(pl1.points.back() == pl2.points.front());
  292. }
  293. }
  294. }
  295. }
  296. SCENARIO("Line distances", "[Geometry]"){
  297. GIVEN("A line"){
  298. Line line(Point(0, 0), Point(20, 0));
  299. THEN("Points on the line segment have 0 distance"){
  300. REQUIRE(line.distance_to(Point(0, 0)) == 0);
  301. REQUIRE(line.distance_to(Point(20, 0)) == 0);
  302. REQUIRE(line.distance_to(Point(10, 0)) == 0);
  303. }
  304. THEN("Points off the line have the appropriate distance"){
  305. REQUIRE(line.distance_to(Point(10, 10)) == 10);
  306. REQUIRE(line.distance_to(Point(50, 0)) == 30);
  307. }
  308. }
  309. }
  310. SCENARIO("Polygon convex/concave detection", "[Geometry]"){
  311. GIVEN(("A Square with dimension 100")){
  312. auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
  313. Point(100,100),
  314. Point(200,100),
  315. Point(200,200),
  316. Point(100,200)}));
  317. THEN("It has 4 convex points counterclockwise"){
  318. REQUIRE(square.concave_points(PI*4/3).size() == 0);
  319. REQUIRE(square.convex_points(PI*2/3).size() == 4);
  320. }
  321. THEN("It has 4 concave points clockwise"){
  322. square.make_clockwise();
  323. REQUIRE(square.concave_points(PI*4/3).size() == 4);
  324. REQUIRE(square.convex_points(PI*2/3).size() == 0);
  325. }
  326. }
  327. GIVEN("A Square with an extra colinearvertex"){
  328. auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
  329. Point(150,100),
  330. Point(200,100),
  331. Point(200,200),
  332. Point(100,200),
  333. Point(100,100)}));
  334. THEN("It has 4 convex points counterclockwise"){
  335. REQUIRE(square.concave_points(PI*4/3).size() == 0);
  336. REQUIRE(square.convex_points(PI*2/3).size() == 4);
  337. }
  338. }
  339. GIVEN("A Square with an extra collinear vertex in different order"){
  340. auto square = Slic3r::Polygon /*new_scale*/(std::vector<Point>({
  341. Point(200,200),
  342. Point(100,200),
  343. Point(100,100),
  344. Point(150,100),
  345. Point(200,100)}));
  346. THEN("It has 4 convex points counterclockwise"){
  347. REQUIRE(square.concave_points(PI*4/3).size() == 0);
  348. REQUIRE(square.convex_points(PI*2/3).size() == 4);
  349. }
  350. }
  351. GIVEN("A triangle"){
  352. auto triangle = Slic3r::Polygon(std::vector<Point>({
  353. Point(16000170,26257364),
  354. Point(714223,461012),
  355. Point(31286371,461008)
  356. }));
  357. THEN("it has three convex vertices"){
  358. REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
  359. REQUIRE(triangle.convex_points(PI*2/3).size() == 3);
  360. }
  361. }
  362. GIVEN("A triangle with an extra collinear point"){
  363. auto triangle = Slic3r::Polygon(std::vector<Point>({
  364. Point(16000170,26257364),
  365. Point(714223,461012),
  366. Point(20000000,461012),
  367. Point(31286371,461012)
  368. }));
  369. THEN("it has three convex vertices"){
  370. REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
  371. REQUIRE(triangle.convex_points(PI*2/3).size() == 3);
  372. }
  373. }
  374. GIVEN("A polygon with concave vertices with angles of specifically 4/3pi"){
  375. // Two concave vertices of this polygon have angle = PI*4/3, so this test fails
  376. // if epsilon is not used.
  377. auto polygon = Slic3r::Polygon(std::vector<Point>({
  378. Point(60246458,14802768),Point(64477191,12360001),
  379. Point(63727343,11060995),Point(64086449,10853608),
  380. Point(66393722,14850069),Point(66034704,15057334),
  381. Point(65284646,13758387),Point(61053864,16200839),
  382. Point(69200258,30310849),Point(62172547,42483120),
  383. Point(61137680,41850279),Point(67799985,30310848),
  384. Point(51399866,1905506),Point(38092663,1905506),
  385. Point(38092663,692699),Point(52100125,692699)
  386. }));
  387. THEN("the correct number of points are detected"){
  388. REQUIRE(polygon.concave_points(PI*4/3).size() == 6);
  389. REQUIRE(polygon.convex_points(PI*2/3).size() == 10);
  390. }
  391. }
  392. }
  393. TEST_CASE("Triangle Simplification does not result in less than 3 points", "[Geometry]"){
  394. auto triangle = Slic3r::Polygon(std::vector<Point>({
  395. Point(16000170,26257364), Point(714223,461012), Point(31286371,461008)
  396. }));
  397. REQUIRE(triangle.simplify(250000).at(0).points.size() == 3);
  398. }
  399. SCENARIO("Ported from xs/t/14_geometry.t", "[Geometry]"){
  400. GIVEN(("square")){
  401. Slic3r::Points points { { 100, 100 }, {100, 200 }, { 200, 200 }, { 200, 100 }, { 150, 150 } };
  402. Slic3r::Polygon hull = Slic3r::Geometry::convex_hull(points);
  403. SECTION("convex hull returns the correct number of points") { REQUIRE(hull.points.size() == 4); }
  404. }
  405. SECTION("arrange returns expected number of positions") {
  406. Pointfs positions;
  407. Slic3r::Geometry::arrange(4, Vec2d(20, 20), 5, nullptr, positions);
  408. REQUIRE(positions.size() == 4);
  409. }
  410. SECTION("directions_parallel") {
  411. REQUIRE(Slic3r::Geometry::directions_parallel(0, 0, 0));
  412. REQUIRE(Slic3r::Geometry::directions_parallel(0, M_PI, 0));
  413. REQUIRE(Slic3r::Geometry::directions_parallel(0, 0, M_PI / 180));
  414. REQUIRE(Slic3r::Geometry::directions_parallel(0, M_PI, M_PI / 180));
  415. REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, M_PI, 0));
  416. REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, PI, M_PI /180));
  417. }
  418. }