test_arc_welder.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. #include <catch2/catch.hpp>
  2. #include <test_utils.hpp>
  3. #include <random>
  4. #include <libslic3r/Geometry/ArcWelder.hpp>
  5. #include <libslic3r/Geometry/Circle.hpp>
  6. #include <libslic3r/SVG.hpp>
  7. #include <libslic3r/libslic3r.h>
  8. using namespace Slic3r;
  9. TEST_CASE("arc basics", "[ArcWelder]") {
  10. using namespace Slic3r::Geometry;
  11. WHEN("arc from { 2000.f, 1000.f } to { 1000.f, 2000.f }") {
  12. Vec2f p1{ 2000.f, 1000.f };
  13. Vec2f p2{ 1000.f, 2000.f };
  14. float r{ 1000.f };
  15. const double s = 1000.f / sqrt(2.);
  16. THEN("90 degrees arc, CCW") {
  17. Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
  18. Vec2f m = ArcWelder::arc_middle_point(p1, p2, r, true);
  19. REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f }));
  20. REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.5 * M_PI));
  21. REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.5 * M_PI).epsilon(0.001));
  22. REQUIRE(is_approx(m, Vec2f{ 1000.f + s, 1000.f + s }, 0.001f));
  23. }
  24. THEN("90 degrees arc, CW") {
  25. Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
  26. Vec2f m = ArcWelder::arc_middle_point(p1, p2, r, false);
  27. REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f }));
  28. REQUIRE(is_approx(m, Vec2f{ 2000.f - s, 2000.f - s }, 0.001f));
  29. }
  30. THEN("270 degrees arc, CCW") {
  31. Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
  32. Vec2f m = ArcWelder::arc_middle_point(p1, p2, - r, true);
  33. REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f }));
  34. REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx(1.5 * M_PI));
  35. REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * 1.5 * M_PI).epsilon(0.001));
  36. REQUIRE(is_approx(m, Vec2f{ 2000.f + s, 2000.f + s }, 0.001f));
  37. }
  38. THEN("270 degrees arc, CW") {
  39. Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
  40. Vec2f m = ArcWelder::arc_middle_point(p1, p2, - r, false);
  41. REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f }));
  42. REQUIRE(is_approx(m, Vec2f{ 1000.f - s, 1000.f - s }, 0.001f));
  43. }
  44. }
  45. WHEN("arc from { 1707.11f, 1707.11f } to { 1000.f, 2000.f }") {
  46. Vec2f p1{ 1707.11f, 1707.11f };
  47. Vec2f p2{ 1000.f, 2000.f };
  48. float r{ 1000.f };
  49. Vec2f center1 = Vec2f{ 1000.f, 1000.f };
  50. // Center on the other side of the CCW arch.
  51. Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1);
  52. THEN("45 degrees arc, CCW") {
  53. Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
  54. REQUIRE(is_approx(c, center1, 1.f));
  55. REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.25 * M_PI));
  56. REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.25 * M_PI).epsilon(0.001));
  57. }
  58. THEN("45 degrees arc, CW") {
  59. Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
  60. REQUIRE(is_approx(c, center2, 1.f));
  61. }
  62. THEN("315 degrees arc, CCW") {
  63. Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
  64. REQUIRE(is_approx(c, center2, 1.f));
  65. REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx((2. - 0.25) * M_PI));
  66. REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 0.25) * M_PI).epsilon(0.001));
  67. }
  68. THEN("315 degrees arc, CW") {
  69. Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
  70. REQUIRE(is_approx(c, center1, 1.f));
  71. }
  72. }
  73. WHEN("arc from { 1866.f, 1500.f } to { 1000.f, 2000.f }") {
  74. Vec2f p1{ 1866.f, 1500.f };
  75. Vec2f p2{ 1000.f, 2000.f };
  76. float r{ 1000.f };
  77. Vec2f center1 = Vec2f{ 1000.f, 1000.f };
  78. // Center on the other side of the CCW arch.
  79. Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1);
  80. THEN("60 degrees arc, CCW") {
  81. Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
  82. REQUIRE(is_approx(c, center1, 1.f));
  83. REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, r), float(M_PI / 3.), 0.001f));
  84. REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * M_PI / 3.).epsilon(0.001));
  85. }
  86. THEN("60 degrees arc, CW") {
  87. Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
  88. REQUIRE(is_approx(c, center2, 1.f));
  89. }
  90. THEN("300 degrees arc, CCW") {
  91. Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
  92. REQUIRE(is_approx(c, center2, 1.f));
  93. REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, - r), float((2. - 1./3.) * M_PI), 0.001f));
  94. REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 1. / 3.) * M_PI).epsilon(0.001));
  95. }
  96. THEN("300 degrees arc, CW") {
  97. Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
  98. REQUIRE(is_approx(c, center1, 1.f));
  99. }
  100. }
  101. }
  102. TEST_CASE("arc discretization", "[ArcWelder]") {
  103. using namespace Slic3r::Geometry;
  104. WHEN("arc from { 2, 1 } to { 1, 2 }") {
  105. const Point p1 = Point::new_scale(2., 1.);
  106. const Point p2 = Point::new_scale(1., 2.);
  107. const Point center = Point::new_scale(1., 1.);
  108. const float radius = scaled<float>(1.);
  109. const float resolution = scaled<float>(0.002);
  110. auto test = [center, resolution, radius](const Point &p1, const Point &p2, const float r, const bool ccw) {
  111. Vec2f c = ArcWelder::arc_center(p1.cast<float>(), p2.cast<float>(), r, ccw);
  112. REQUIRE((p1.cast<float>() - c).norm() == Approx(radius));
  113. REQUIRE((c - center.cast<float>()).norm() == Approx(0.));
  114. Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
  115. REQUIRE(pts.size() >= 2);
  116. REQUIRE(pts.front() == p1);
  117. REQUIRE(pts.back() == p2);
  118. for (const Point &p : pts)
  119. REQUIRE(std::abs((p.cast<double>() - c.cast<double>()).norm() - double(radius)) < double(resolution + SCALED_EPSILON));
  120. };
  121. THEN("90 degrees arc, CCW") {
  122. test(p1, p2, radius, true);
  123. }
  124. THEN("270 degrees arc, CCW") {
  125. test(p2, p1, - radius, true);
  126. }
  127. THEN("90 degrees arc, CW") {
  128. test(p2, p1, radius, false);
  129. }
  130. THEN("270 degrees arc, CW") {
  131. test(p1, p2, - radius, false);
  132. }
  133. }
  134. }
  135. void test_arc_fit_variance(const Point &p1, const Point &p2, const float r, const float r_fit, const bool ccw, const Points::const_iterator begin, const Points::const_iterator end)
  136. {
  137. using namespace Slic3r::Geometry;
  138. double variance = ArcWelder::arc_fit_variance(p1, p2, r, ccw, begin, end);
  139. double variance_fit = ArcWelder::arc_fit_variance(p1, p2, r_fit, ccw, begin, end);
  140. REQUIRE(variance_fit < variance);
  141. };
  142. void test_arc_fit_max_deviation(const Point &p1, const Point &p2, const float r, const float r_fit, const bool ccw, const Points::const_iterator begin, const Points::const_iterator end)
  143. {
  144. using namespace Slic3r::Geometry;
  145. double max_deviation = ArcWelder::arc_fit_max_deviation(p1, p2, r, ccw, begin, end);
  146. double max_deviation_fit = ArcWelder::arc_fit_max_deviation(p1, p2, r_fit, ccw, begin, end);
  147. REQUIRE(std::abs(max_deviation_fit) < std::abs(max_deviation));
  148. };
  149. void test_arc_fit(const Point &p1, const Point &p2, const float r, const float r_fit, const bool ccw, const Points::const_iterator begin, const Points::const_iterator end)
  150. {
  151. test_arc_fit_variance(p1, p2, r, r_fit, ccw, begin, end);
  152. test_arc_fit_max_deviation(p1, p2, r, r_fit, ccw, begin, end);
  153. };
  154. TEST_CASE("arc fitting", "[ArcWelder]") {
  155. using namespace Slic3r::Geometry;
  156. WHEN("arc from { 2, 1 } to { 1, 2 }") {
  157. const Point p1 = Point::new_scale(2., 1.);
  158. const Point p2 = Point::new_scale(1., 2.);
  159. const Point center = Point::new_scale(1., 1.);
  160. const float radius = scaled<float>(1.);
  161. const float resolution = scaled<float>(0.002);
  162. auto test = [center, resolution](const Point &p1, const Point &p2, const float r, const bool ccw) {
  163. Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
  164. ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution);
  165. REQUIRE(path.size() == 2);
  166. REQUIRE(path.front().point == p1);
  167. REQUIRE(path.front().radius == 0.f);
  168. REQUIRE(path.back().point == p2);
  169. REQUIRE(path.back().ccw() == ccw);
  170. test_arc_fit(p1, p2, r, path.back().radius, true, pts.begin(), pts.end());
  171. };
  172. THEN("90 degrees arc, CCW is fitted") {
  173. test(p1, p2, radius, true);
  174. }
  175. THEN("270 degrees arc, CCW is fitted") {
  176. test(p2, p1, - radius, true);
  177. }
  178. THEN("90 degrees arc, CW is fitted") {
  179. test(p2, p1, radius, false);
  180. }
  181. THEN("270 degrees arc, CW is fitted") {
  182. test(p1, p2, - radius, false);
  183. }
  184. }
  185. WHEN("arc from { 2, 1 } to { 1, 2 }, another arc from { 2, 1 } to { 0, 2 }, tangentially connected") {
  186. const Point p1 = Point::new_scale(2., 1.);
  187. const Point p2 = Point::new_scale(1., 2.);
  188. const Point p3 = Point::new_scale(0., 3.);
  189. const Point center1 = Point::new_scale(1., 1.);
  190. const Point center2 = Point::new_scale(1., 3.);
  191. const float radius = scaled<float>(1.);
  192. const float resolution = scaled<float>(0.002);
  193. auto test = [center1, center2, resolution](const Point &p1, const Point &p2, const Point &p3, const float r, const bool ccw) {
  194. Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
  195. size_t num_pts1 = pts.size();
  196. {
  197. Points pts2 = ArcWelder::arc_discretize(p2, p3, - r, ! ccw, resolution);
  198. REQUIRE(pts.back() == pts2.front());
  199. pts.insert(pts.end(), std::next(pts2.begin()), pts2.end());
  200. }
  201. ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution);
  202. REQUIRE(path.size() == 3);
  203. REQUIRE(path.front().point == p1);
  204. REQUIRE(path.front().radius == 0.f);
  205. REQUIRE(path[1].point == p2);
  206. REQUIRE(path[1].ccw() == ccw);
  207. REQUIRE(path.back().point == p3);
  208. REQUIRE(path.back().ccw() == ! ccw);
  209. test_arc_fit(p1, p2, r, path[1].radius, ccw, pts.begin(), pts.begin() + num_pts1);
  210. test_arc_fit(p2, p3, - r, path.back().radius, ! ccw, pts.begin() + num_pts1 - 1, pts.end());
  211. };
  212. THEN("90 degrees arches, CCW are fitted") {
  213. test(p1, p2, p3, radius, true);
  214. }
  215. THEN("270 degrees arc, CCW is fitted") {
  216. test(p3, p2, p1, -radius, true);
  217. }
  218. THEN("90 degrees arc, CW is fitted") {
  219. test(p3, p2, p1, radius, false);
  220. }
  221. THEN("270 degrees arc, CW is fitted") {
  222. test(p1, p2, p3, -radius, false);
  223. }
  224. }
  225. }
  226. TEST_CASE("least squares arc fitting, interpolating end points", "[ArcWelder]") {
  227. using namespace Slic3r::Geometry;
  228. // Generate bunch of random arches.
  229. const coord_t max_coordinate = scaled<coord_t>(sqrt(250. - 1.));
  230. static constexpr const double min_radius = scaled<double>(0.01);
  231. static constexpr const double max_radius = scaled<double>(250.);
  232. // static constexpr const double deviation = scaled<double>(0.5);
  233. static constexpr const double deviation = scaled<double>(0.1);
  234. // Seeded with a fixed seed, to be repeatable.
  235. std::mt19937 rng(867092346);
  236. std::uniform_int_distribution<int32_t> coord_sampler(0, int32_t(max_coordinate));
  237. std::uniform_real_distribution<double> angle_sampler(0.001, 2. * M_PI - 0.001);
  238. std::uniform_real_distribution<double> radius_sampler(min_radius, max_radius);
  239. std::uniform_int_distribution<int> num_samples_sampler(1, 100);
  240. auto test_arc_fitting = [&rng, &coord_sampler, &num_samples_sampler, &angle_sampler, &radius_sampler]() {
  241. auto sample_point = [&rng, &coord_sampler]() {
  242. return Vec2d(coord_sampler(rng), coord_sampler(rng));
  243. };
  244. // Start and end point of the arc:
  245. Vec2d center_pos = sample_point();
  246. double angle0 = angle_sampler(rng);
  247. double angle = angle_sampler(rng);
  248. double radius = radius_sampler(rng);
  249. Vec2d v1 = Eigen::Rotation2D(angle0) * Vec2d(1., 0.);
  250. Vec2d v2 = Eigen::Rotation2D(angle0 + angle) * Vec2d(1., 0.);
  251. Vec2d start_pos = center_pos + radius * v1;
  252. Vec2d end_pos = center_pos + radius * v2;
  253. std::vector<Vec2d> samples;
  254. size_t num_samples = num_samples_sampler(rng);
  255. for (size_t i = 0; i < num_samples; ++ i) {
  256. double sample_r = sqrt(std::uniform_real_distribution<double>(sqr(std::max(0., radius - deviation)), sqr(radius + deviation))(rng));
  257. double sample_a = std::uniform_real_distribution<double>(0., angle)(rng);
  258. Vec2d pt = center_pos + Eigen::Rotation2D(angle0 + sample_a) * Vec2d(sample_r, 0.);
  259. samples.emplace_back(pt);
  260. assert((pt - center_pos).norm() > radius - deviation - SCALED_EPSILON);
  261. assert((pt - center_pos).norm() < radius + deviation + SCALED_EPSILON);
  262. }
  263. // Vec2d new_center = ArcWelder::arc_fit_center_algebraic_ls(start_pos, end_pos, center_pos, samples.begin(), samples.end());
  264. THEN("Center is fitted correctly") {
  265. std::optional<Vec2d> new_center_opt = ArcWelder::arc_fit_center_gauss_newton_ls(start_pos, end_pos, center_pos, samples.begin(), samples.end(), 10);
  266. REQUIRE(new_center_opt);
  267. if (new_center_opt) {
  268. Vec2d new_center = *new_center_opt;
  269. double total_deviation = 0;
  270. double new_total_deviation = 0;
  271. for (const Vec2d &s : samples) {
  272. total_deviation += sqr((s - center_pos).norm() - radius);
  273. new_total_deviation += sqr((s - new_center).norm() - radius);
  274. }
  275. total_deviation /= double(num_samples);
  276. new_total_deviation /= double(num_samples);
  277. REQUIRE(new_total_deviation <= total_deviation);
  278. #if 0
  279. double dist = (center_pos - new_center).norm();
  280. printf("Radius: %lf, Angle: %lf deg, Samples: %d, Dist: %lf\n", unscaled<double>(radius), 180. * angle / M_PI, int(num_samples), unscaled<double>(dist));
  281. // REQUIRE(is_approx(center_pos, new_center, deviation));
  282. if (dist > scaled<double>(1.)) {
  283. static int irun = 0;
  284. char path[2048];
  285. sprintf(path, "d:\\temp\\debug\\circle-fit-%d.svg", irun++);
  286. Eigen::AlignedBox<double, 2> bbox(start_pos, end_pos);
  287. for (const Vec2d& sample : samples)
  288. bbox.extend(sample);
  289. bbox.extend(center_pos);
  290. Slic3r::SVG svg(path, BoundingBox(bbox.min().cast<coord_t>(), bbox.max().cast<coord_t>()).inflated(bbox.sizes().maxCoeff() * 0.05));
  291. Points arc_src;
  292. for (size_t i = 0; i <= 1000; ++i)
  293. arc_src.emplace_back((center_pos + Eigen::Rotation2D(angle0 + double(i) * angle / 1000.) * Vec2d(radius, 0.)).cast<coord_t>());
  294. svg.draw(Polyline(arc_src));
  295. Points arc_new;
  296. double new_arc_angle = ArcWelder::arc_angle(start_pos, end_pos, (new_center - start_pos).norm());
  297. for (size_t i = 0; i <= 1000; ++i)
  298. arc_new.emplace_back((new_center + Eigen::Rotation2D(double(i) * new_arc_angle / 1000.) * (start_pos - new_center)).cast<coord_t>());
  299. svg.draw(Polyline(arc_new), "magenta");
  300. svg.draw(Point(start_pos.cast<coord_t>()), "blue");
  301. svg.draw(Point(end_pos.cast<coord_t>()), "blue");
  302. svg.draw(Point(center_pos.cast<coord_t>()), "blue");
  303. for (const Vec2d &sample : samples)
  304. svg.draw(Point(sample.cast<coord_t>()), "red");
  305. svg.draw(Point(new_center.cast<coord_t>()), "magenta");
  306. }
  307. if (!is_approx(center_pos, new_center, scaled<double>(5.))) {
  308. printf("Failed\n");
  309. }
  310. #endif
  311. } else {
  312. printf("Fitting failed\n");
  313. }
  314. }
  315. };
  316. WHEN("Generating a random arc and randomized arc samples") {
  317. for (size_t i = 0; i < 1000; ++ i)
  318. test_arc_fitting();
  319. }
  320. }
  321. TEST_CASE("arc wedge test", "[ArcWelder]") {
  322. using namespace Slic3r::Geometry;
  323. WHEN("test point inside wedge, arc from { 2, 1 } to { 1, 2 }") {
  324. const int64_t s = 1000000;
  325. const Vec2i64 p1{ 2 * s, s };
  326. const Vec2i64 p2{ s, 2 * s };
  327. const Vec2i64 center{ s, s };
  328. const int64_t radius{ s };
  329. auto test = [center](
  330. // Arc data
  331. const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw,
  332. // Test data
  333. const Vec2i64 &ptest, const bool ptest_inside) {
  334. const Vec2d c = ArcWelder::arc_center(p1.cast<double>(), p2.cast<double>(), double(r), ccw);
  335. REQUIRE(is_approx(c, center.cast<double>()));
  336. REQUIRE(ArcWelder::inside_arc_wedge(p1, p2, center, r > 0, ccw, ptest) == ptest_inside);
  337. REQUIRE(ArcWelder::inside_arc_wedge(p1.cast<double>(), p2.cast<double>(), double(r), ccw, ptest.cast<double>()) == ptest_inside);
  338. };
  339. auto test_quadrants = [center, test](
  340. // Arc data
  341. const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw,
  342. // Test data
  343. const Vec2i64 &ptest1, const bool ptest_inside1,
  344. const Vec2i64 &ptest2, const bool ptest_inside2,
  345. const Vec2i64 &ptest3, const bool ptest_inside3,
  346. const Vec2i64 &ptest4, const bool ptest_inside4) {
  347. test(p1, p2, r, ccw, ptest1 + center, ptest_inside1);
  348. test(p1, p2, r, ccw, ptest2 + center, ptest_inside2);
  349. test(p1, p2, r, ccw, ptest3 + center, ptest_inside3);
  350. test(p1, p2, r, ccw, ptest4 + center, ptest_inside4);
  351. };
  352. THEN("90 degrees arc, CCW") {
  353. test_quadrants(p1, p2, radius, true,
  354. Vec2i64{ s, s }, true,
  355. Vec2i64{ s, - s }, false,
  356. Vec2i64{ - s, s }, false,
  357. Vec2i64{ - s, - s }, false);
  358. }
  359. THEN("270 degrees arc, CCW") {
  360. test_quadrants(p2, p1, -radius, true,
  361. Vec2i64{ s, s }, false,
  362. Vec2i64{ s, - s }, true,
  363. Vec2i64{ - s, s }, true,
  364. Vec2i64{ - s, - s }, true);
  365. }
  366. THEN("90 degrees arc, CW") {
  367. test_quadrants(p2, p1, radius, false,
  368. Vec2i64{ s, s }, true,
  369. Vec2i64{ s, - s }, false,
  370. Vec2i64{ - s, s }, false,
  371. Vec2i64{ - s, - s }, false);
  372. }
  373. THEN("270 degrees arc, CW") {
  374. test_quadrants(p1, p2, -radius, false,
  375. Vec2i64{ s, s }, false,
  376. Vec2i64{ s, - s }, true,
  377. Vec2i64{ - s, s }, true,
  378. Vec2i64{ - s, - s }, true);
  379. }
  380. }
  381. }
  382. #if 0
  383. // For quantization
  384. //#include <libslic3r/GCode/GCodeWriter.hpp>
  385. TEST_CASE("arc quantization", "[ArcWelder]") {
  386. using namespace Slic3r::Geometry;
  387. WHEN("generating a bunch of random arches") {
  388. static constexpr const size_t len = 100000;
  389. static constexpr const coord_t max_coordinate = scaled<coord_t>(250.);
  390. static constexpr const float max_radius = scaled<float>(250.);
  391. ArcWelder::Segments path;
  392. path.reserve(len + 1);
  393. // Seeded with a fixed seed, to be repeatable.
  394. std::mt19937 rng(987432690);
  395. // Generate bunch of random arches.
  396. std::uniform_int_distribution<int32_t> coord_sampler(0, int32_t(max_coordinate));
  397. std::uniform_real_distribution<float> radius_sampler(- max_radius, max_radius);
  398. std::uniform_int_distribution<int> orientation_sampler(0, 1);
  399. path.push_back({ Point{coord_sampler(rng), coord_sampler(rng)}, 0, ArcWelder::Orientation::CCW });
  400. for (size_t i = 0; i < len; ++ i) {
  401. ArcWelder::Segment seg { Point{coord_sampler(rng), coord_sampler(rng)}, radius_sampler(rng), orientation_sampler(rng) ? ArcWelder::Orientation::CCW : ArcWelder::Orientation::CW };
  402. while ((seg.point.cast<double>() - path.back().point.cast<double>()).norm() > 2. * std::abs(seg.radius))
  403. seg = { Point{coord_sampler(rng), coord_sampler(rng)}, radius_sampler(rng), orientation_sampler(rng) ? ArcWelder::Orientation::CCW : ArcWelder::Orientation::CW };
  404. path.push_back(seg);
  405. }
  406. // Run the test, quantize coordinates and radius, find the maximum error of quantization comparing the two arches.
  407. struct ArcEval {
  408. double error;
  409. double radius;
  410. double angle;
  411. };
  412. std::vector<ArcEval> center_errors_R;
  413. center_errors_R.reserve(len);
  414. std::vector<double> center_errors_R_exact;
  415. center_errors_R_exact.reserve(len);
  416. std::vector<double> center_errors_IJ;
  417. center_errors_IJ.reserve(len);
  418. for (size_t i = 0; i < len; ++ i) {
  419. // Source arc:
  420. const Vec2d start_point = unscaled<double>(path[i].point);
  421. const Vec2d end_point = unscaled<double>(path[i + 1].point);
  422. const double radius = unscaled<double>(path[i + 1].radius);
  423. const bool ccw = path[i + 1].ccw();
  424. const Vec2d center = ArcWelder::arc_center(start_point, end_point, radius, ccw);
  425. {
  426. const double d1 = (start_point - center).norm();
  427. const double d2 = (end_point - center).norm();
  428. const double dx = (end_point - start_point).norm();
  429. assert(std::abs(d1 - std::abs(radius)) < EPSILON);
  430. assert(std::abs(d2 - std::abs(radius)) < EPSILON);
  431. }
  432. // Quantized arc:
  433. const Vec2d start_point_quantized { GCodeFormatter::quantize_xyzf(start_point.x()), GCodeFormatter::quantize_xyzf(start_point.y()) };
  434. const Vec2d end_point_quantized { GCodeFormatter::quantize_xyzf(end_point .x()), GCodeFormatter::quantize_xyzf(end_point .y()) };
  435. const double radius_quantized { GCodeFormatter::quantize_xyzf(radius) };
  436. const Vec2d center_quantized { GCodeFormatter::quantize_xyzf(center .x()), GCodeFormatter::quantize_xyzf(center .y()) };
  437. // Evaulate maximum error for the quantized arc given by the end points and radius.
  438. const Vec2d center_from_quantized = ArcWelder::arc_center(start_point_quantized, end_point_quantized, radius, ccw);
  439. const Vec2d center_reconstructed = ArcWelder::arc_center(start_point_quantized, end_point_quantized, radius_quantized, ccw);
  440. #if 0
  441. center_errors_R.push_back({
  442. std::abs((center_reconstructed - center).norm()),
  443. radius,
  444. ArcWelder::arc_angle(start_point, end_point, radius) * 180. / M_PI
  445. });
  446. if (center_errors_R.back().error > 0.15)
  447. printf("Fuj\n");
  448. #endif
  449. center_errors_R_exact.emplace_back(std::abs((center_from_quantized - center).norm()));
  450. if (center_errors_R_exact.back() > 0.15)
  451. printf("Fuj\n");
  452. center_errors_IJ.emplace_back(std::abs((center_quantized - center).norm()));
  453. if (center_errors_IJ.back() > 0.15)
  454. printf("Fuj\n");
  455. // Adjust center of the arc to the quantized end points.
  456. Vec2d third_point = ArcWelder::arc_middle_point(start_point, end_point, radius, ccw);
  457. double third_point_radius = (third_point - center).norm();
  458. assert(std::abs(third_point_radius - std::abs(radius)) < EPSILON);
  459. std::optional<Vec2d> center_adjusted = try_circle_center(start_point_quantized, end_point_quantized, third_point, EPSILON);
  460. //assert(center_adjusted);
  461. if (center_adjusted) {
  462. const double radius_adjusted = (center_adjusted.value() - start_point_quantized).norm() * (radius > 0 ? 1. : -1.);
  463. const double radius_adjusted_quantized = GCodeFormatter::quantize_xyzf(radius_adjusted);
  464. // Evaulate maximum error for the quantized arc given by the end points and radius.
  465. const Vec2f center_reconstructed = ArcWelder::arc_center(start_point_quantized.cast<float>(), end_point_quantized.cast<float>(), float(radius_adjusted_quantized), ccw);
  466. double rtest = std::abs(radius_adjusted_quantized);
  467. double d1 = std::abs((center_reconstructed - start_point.cast<float>()).norm() - rtest);
  468. double d2 = std::abs((center_reconstructed - end_point.cast<float>()).norm() - rtest);
  469. double d3 = std::abs((center_reconstructed - third_point.cast<float>()).norm() - rtest);
  470. double d = std::max(d1, std::max(d2, d3));
  471. center_errors_R.push_back({
  472. d,
  473. radius,
  474. ArcWelder::arc_angle(start_point, end_point, radius) * 180. / M_PI
  475. });
  476. } else {
  477. printf("Adjusted circle is collinear.\n");
  478. }
  479. }
  480. std::sort(center_errors_R.begin(), center_errors_R.end(), [](auto l, auto r) { return l.error > r.error; });
  481. std::sort(center_errors_R_exact.begin(), center_errors_R_exact.end(), [](auto l, auto r) { return l > r; });
  482. std::sort(center_errors_IJ.begin(), center_errors_IJ.end(), [](auto l, auto r) { return l > r; });
  483. printf("Maximum error R: %lf\n", center_errors_R.back().error);
  484. printf("Maximum error R exact: %lf\n", center_errors_R_exact.back());
  485. printf("Maximum error IJ: %lf\n", center_errors_IJ.back());
  486. }
  487. }
  488. #endif