test_marchingsquares.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. #define NOMINMAX
  2. #include <catch2/catch.hpp>
  3. #include <test_utils.hpp>
  4. #include <fstream>
  5. #include <libslic3r/MarchingSquares.hpp>
  6. #include <libslic3r/SLA/RasterToPolygons.hpp>
  7. #include <libslic3r/SLA/AGGRaster.hpp>
  8. #include <libslic3r/MTUtils.hpp>
  9. #include <libslic3r/SVG.hpp>
  10. #include <libslic3r/ClipperUtils.hpp>
  11. #include <libslic3r/TriangleMeshSlicer.hpp>
  12. #include <libslic3r/TriangulateWall.hpp>
  13. #include <libslic3r/Tesselate.hpp>
  14. #include <libslic3r/SlicesToTriangleMesh.hpp>
  15. using namespace Slic3r;
  16. static double area(const sla::RasterBase::PixelDim &pxd)
  17. {
  18. return pxd.w_mm * pxd.h_mm;
  19. }
  20. static Slic3r::sla::RasterGrayscaleAA create_raster(
  21. const sla::RasterBase::Resolution &res,
  22. double disp_w = 100.,
  23. double disp_h = 100.)
  24. {
  25. sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
  26. auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
  27. sla::RasterBase::Trafo trafo;
  28. trafo.center_x = bb.center().x();
  29. trafo.center_y = bb.center().y();
  30. return sla::RasterGrayscaleAA{res, pixdim, trafo, agg::gamma_threshold(.5)};
  31. }
  32. static ExPolygon square(double a, Point center = {0, 0})
  33. {
  34. ExPolygon poly;
  35. coord_t V = scaled(a / 2.);
  36. poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}};
  37. poly.translate(center.x(), center.y());
  38. return poly;
  39. }
  40. static ExPolygon square_with_hole(double a, Point center = {0, 0})
  41. {
  42. ExPolygon poly = square(a);
  43. poly.holes.emplace_back();
  44. coord_t V = scaled(a / 4.);
  45. poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}};
  46. poly.translate(center.x(), center.y());
  47. return poly;
  48. }
  49. static ExPolygons circle_with_hole(double r, Point center = {0, 0}) {
  50. ExPolygon poly;
  51. std::vector<double> pis = linspace_vector(0., 2 * PI, 100);
  52. coord_t rs = scaled(r);
  53. for (double phi : pis) {
  54. poly.contour.points.emplace_back(rs * std::cos(phi), rs * std::sin(phi));
  55. }
  56. poly.holes.emplace_back(poly.contour);
  57. poly.holes.front().reverse();
  58. for (auto &p : poly.holes.front().points) p /= 2;
  59. poly.translate(center.x(), center.y());
  60. return {poly};
  61. }
  62. static const Vec2i32 W4x4 = {4, 4};
  63. static const Vec2i32 W2x2 = {2, 2};
  64. template<class Rst>
  65. static void test_expolys(Rst && rst,
  66. const ExPolygons & ref,
  67. Vec2i32 window,
  68. const std::string &name = "test")
  69. {
  70. for (const ExPolygon &expoly : ref) rst.draw(expoly);
  71. std::fstream out(name + ".png", std::ios::out);
  72. out << rst.encode(sla::PNGRasterEncoder{});
  73. out.close();
  74. ExPolygons extracted = sla::raster_to_polygons(rst, window);
  75. SVG svg(name + ".svg");
  76. svg.draw(extracted);
  77. svg.draw(ref, "green");
  78. svg.Close();
  79. double max_rel_err = 0.1;
  80. sla::RasterBase::PixelDim pxd = rst.pixel_dimensions();
  81. double max_abs_err = area(pxd) * scaled(1.) * scaled(1.);
  82. BoundingBox ref_bb;
  83. for (auto &expoly : ref) ref_bb.merge(expoly.contour.bounding_box());
  84. double max_displacement = 4. * (std::pow(pxd.h_mm, 2) + std::pow(pxd.w_mm, 2));
  85. max_displacement *= scaled<double>(1.) * scaled(1.);
  86. REQUIRE(extracted.size() == ref.size());
  87. for (size_t i = 0; i < ref.size(); ++i) {
  88. REQUIRE(extracted[i].contour.is_counter_clockwise());
  89. REQUIRE(extracted[i].holes.size() == ref[i].holes.size());
  90. for (auto &h : extracted[i].holes) REQUIRE(h.is_clockwise());
  91. double refa = ref[i].area();
  92. double abs_err = std::abs(extracted[i].area() - refa);
  93. double rel_err = abs_err / refa;
  94. REQUIRE((rel_err <= max_rel_err || abs_err <= max_abs_err));
  95. BoundingBox bb;
  96. for (auto &expoly : extracted) bb.merge(expoly.contour.bounding_box());
  97. Point d = bb.center() - ref_bb.center();
  98. REQUIRE(double(d.transpose() * d) <= max_displacement);
  99. }
  100. }
  101. TEST_CASE("Empty raster should result in empty polygons", "[MarchingSquares]") {
  102. sla::RasterGrayscaleAAGammaPower rst{{}, {}, {}};
  103. ExPolygons extracted = sla::raster_to_polygons(rst);
  104. REQUIRE(extracted.size() == 0);
  105. }
  106. TEST_CASE("Marching squares directions", "[MarchingSquares]") {
  107. marchsq::Coord crd{1, 1};
  108. REQUIRE(step(crd, marchsq::__impl::Dir::left).r == 1);
  109. REQUIRE(step(crd, marchsq::__impl::Dir::left).c == 0);
  110. REQUIRE(step(crd, marchsq::__impl::Dir::down).r == 2);
  111. REQUIRE(step(crd, marchsq::__impl::Dir::down).c == 1);
  112. REQUIRE(step(crd, marchsq::__impl::Dir::right).r == 1);
  113. REQUIRE(step(crd, marchsq::__impl::Dir::right).c == 2);
  114. REQUIRE(step(crd, marchsq::__impl::Dir::up).r == 0);
  115. REQUIRE(step(crd, marchsq::__impl::Dir::up).c == 1);
  116. }
  117. TEST_CASE("Fully covered raster should result in a rectangle", "[MarchingSquares]") {
  118. auto rst = create_raster({4, 4}, 4., 4.);
  119. ExPolygon rect = square(4);
  120. SECTION("Full accuracy") {
  121. test_expolys(rst, {rect}, W2x2, "fully_covered_full_acc");
  122. }
  123. SECTION("Half accuracy") {
  124. test_expolys(rst, {rect}, W4x4, "fully_covered_half_acc");
  125. }
  126. }
  127. TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") {
  128. sla::RasterBase::PixelDim pixdim{1, 1};
  129. // We need one additional row and column to detect edges
  130. sla::RasterGrayscaleAA rst{{4, 4}, pixdim, {}, agg::gamma_threshold(.5)};
  131. // Draw a triangle from individual pixels
  132. rst.draw(square(1., {0500000, 0500000}));
  133. rst.draw(square(1., {1500000, 0500000}));
  134. rst.draw(square(1., {2500000, 0500000}));
  135. rst.draw(square(1., {1500000, 1500000}));
  136. rst.draw(square(1., {2500000, 1500000}));
  137. rst.draw(square(1., {2500000, 2500000}));
  138. std::fstream out("4x4.png", std::ios::out);
  139. out << rst.encode(sla::PNGRasterEncoder{});
  140. out.close();
  141. ExPolygons extracted = sla::raster_to_polygons(rst);
  142. SVG svg("4x4.svg");
  143. svg.draw(extracted);
  144. svg.Close();
  145. REQUIRE(extracted.size() == 1);
  146. }
  147. TEST_CASE("4x4 raster with two rings", "[MarchingSquares]") {
  148. sla::RasterBase::PixelDim pixdim{1, 1};
  149. // We need one additional row and column to detect edges
  150. sla::RasterGrayscaleAA rst{{5, 5}, pixdim, {}, agg::gamma_threshold(.5)};
  151. SECTION("Ambiguous case with 'ac' square") {
  152. // Draw a triangle from individual pixels
  153. rst.draw(square(1., {3500000, 2500000}));
  154. rst.draw(square(1., {3500000, 3500000}));
  155. rst.draw(square(1., {2500000, 3500000}));
  156. rst.draw(square(1., {2500000, 1500000}));
  157. rst.draw(square(1., {1500000, 1500000}));
  158. rst.draw(square(1., {1500000, 2500000}));
  159. std::fstream out("4x4_ac.png", std::ios::out);
  160. out << rst.encode(sla::PNGRasterEncoder{});
  161. out.close();
  162. ExPolygons extracted = sla::raster_to_polygons(rst);
  163. SVG svg("4x4_ac.svg");
  164. svg.draw(extracted);
  165. svg.Close();
  166. REQUIRE(extracted.size() == 2);
  167. }
  168. SECTION("Ambiguous case with 'bd' square") {
  169. // Draw a triangle from individual pixels
  170. rst.draw(square(1., {3500000, 1500000}));
  171. rst.draw(square(1., {3500000, 2500000}));
  172. rst.draw(square(1., {2500000, 1500000}));
  173. rst.draw(square(1., {1500000, 2500000}));
  174. rst.draw(square(1., {1500000, 3500000}));
  175. rst.draw(square(1., {2500000, 3500000}));
  176. std::fstream out("4x4_bd.png", std::ios::out);
  177. out << rst.encode(sla::PNGRasterEncoder{});
  178. out.close();
  179. ExPolygons extracted = sla::raster_to_polygons(rst);
  180. SVG svg("4x4_bd.svg");
  181. svg.draw(extracted);
  182. svg.Close();
  183. REQUIRE(extracted.size() == 2);
  184. }
  185. }
  186. TEST_CASE("Square with hole in the middle", "[MarchingSquares]") {
  187. using namespace Slic3r;
  188. ExPolygons inp = {square_with_hole(50.)};
  189. SECTION("Proportional raster, 1x1 mm pixel size, full accuracy") {
  190. test_expolys(create_raster({100, 100}, 100., 100.), inp, W2x2, "square_with_hole_proportional_1x1_mm_px_full");
  191. }
  192. SECTION("Proportional raster, 1x1 mm pixel size, half accuracy") {
  193. test_expolys(create_raster({100, 100}, 100., 100.), inp, W4x4, "square_with_hole_proportional_1x1_mm_px_half");
  194. }
  195. SECTION("Landscape raster, 1x1 mm pixel size, full accuracy") {
  196. test_expolys(create_raster({150, 100}, 150., 100.), inp, W2x2, "square_with_hole_landsc_1x1_mm_px_full");
  197. }
  198. SECTION("Landscape raster, 1x1 mm pixel size, half accuracy") {
  199. test_expolys(create_raster({150, 100}, 150., 100.), inp, W4x4, "square_with_hole_landsc_1x1_mm_px_half");
  200. }
  201. SECTION("Portrait raster, 1x1 mm pixel size, full accuracy") {
  202. test_expolys(create_raster({100, 150}, 100., 150.), inp, W2x2, "square_with_hole_portrait_1x1_mm_px_full");
  203. }
  204. SECTION("Portrait raster, 1x1 mm pixel size, half accuracy") {
  205. test_expolys(create_raster({100, 150}, 100., 150.), inp, W4x4, "square_with_hole_portrait_1x1_mm_px_half");
  206. }
  207. SECTION("Proportional raster, 2x2 mm pixel size, full accuracy") {
  208. test_expolys(create_raster({200, 200}, 100., 100.), inp, W2x2, "square_with_hole_proportional_2x2_mm_px_full");
  209. }
  210. SECTION("Proportional raster, 2x2 mm pixel size, half accuracy") {
  211. test_expolys(create_raster({200, 200}, 100., 100.), inp, W4x4, "square_with_hole_proportional_2x2_mm_px_half");
  212. }
  213. SECTION("Proportional raster, 0.5x0.5 mm pixel size, full accuracy") {
  214. test_expolys(create_raster({50, 50}, 100., 100.), inp, W2x2, "square_with_hole_proportional_0.5x0.5_mm_px_full");
  215. }
  216. SECTION("Proportional raster, 0.5x0.5 mm pixel size, half accuracy") {
  217. test_expolys(create_raster({50, 50}, 100., 100.), inp, W4x4, "square_with_hole_proportional_0.5x0.5_mm_px_half");
  218. }
  219. }
  220. TEST_CASE("Circle with hole in the middle", "[MarchingSquares]") {
  221. using namespace Slic3r;
  222. test_expolys(create_raster({1000, 1000}), circle_with_hole(25.), W2x2, "circle_with_hole");
  223. }
  224. static void recreate_object_from_rasters(const std::string &objname, float lh) {
  225. TriangleMesh mesh = load_model(objname);
  226. auto bb = mesh.bounding_box();
  227. Vec3f tr = -bb.center().cast<float>();
  228. mesh.translate(tr.x(), tr.y(), tr.z());
  229. bb = mesh.bounding_box();
  230. std::vector<ExPolygons> layers = slice_mesh_ex(mesh.its, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh));
  231. sla::RasterBase::Resolution res{2560, 1440};
  232. double disp_w = 120.96;
  233. double disp_h = 68.04;
  234. #ifndef NDEBUG
  235. size_t cntr = 0;
  236. #endif
  237. for (ExPolygons &layer : layers) {
  238. auto rst = create_raster(res, disp_w, disp_h);
  239. for (ExPolygon &island : layer) {
  240. rst.draw(island);
  241. }
  242. #ifndef NDEBUG
  243. std::fstream out(objname + std::to_string(cntr) + ".png", std::ios::out);
  244. out << rst.encode(sla::PNGRasterEncoder{});
  245. out.close();
  246. #endif
  247. ExPolygons layer_ = sla::raster_to_polygons(rst);
  248. // float delta = scaled(std::min(rst.pixel_dimensions().h_mm,
  249. // rst.pixel_dimensions().w_mm)) / 2;
  250. // layer_ = expolygons_simplify(layer_, delta);
  251. #ifndef NDEBUG
  252. SVG svg(objname + std::to_string(cntr) + ".svg", BoundingBox(Point{0, 0}, Point{scaled(disp_w), scaled(disp_h)}));
  253. svg.draw(layer_);
  254. svg.draw(layer, "green");
  255. svg.Close();
  256. #endif
  257. double layera = 0., layera_ = 0.;
  258. for (auto &p : layer) layera += p.area();
  259. for (auto &p : layer_) layera_ += p.area();
  260. #ifndef NDEBUG
  261. std::cout << cntr++ << std::endl;
  262. #endif
  263. double diff = std::abs(layera_ - layera);
  264. REQUIRE((diff <= 0.1 * layera || diff < scaled<double>(1.) * scaled<double>(1.)));
  265. layer = std::move(layer_);
  266. }
  267. indexed_triangle_set out = slices_to_mesh(layers, bb.min.z(), double(lh), double(lh));
  268. its_write_obj(out, "out_from_rasters.obj");
  269. }
  270. TEST_CASE("Recreate object from rasters", "[SL1Import]") {
  271. recreate_object_from_rasters("frog_legs.obj", 0.05f);
  272. }