From 34d9b0678b5f0ce97dfb10a1de8ab48585fa2503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 17 May 2024 15:06:40 +0200 Subject: [PATCH 1/4] Add missing header guards to VoronoiVisualUtils. --- src/libslic3r/Geometry/VoronoiVisualUtils.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libslic3r/Geometry/VoronoiVisualUtils.hpp b/src/libslic3r/Geometry/VoronoiVisualUtils.hpp index c538d9e872..71bf4050fb 100644 --- a/src/libslic3r/Geometry/VoronoiVisualUtils.hpp +++ b/src/libslic3r/Geometry/VoronoiVisualUtils.hpp @@ -2,6 +2,9 @@ ///|/ ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher ///|/ +#ifndef slic3r_VoronoiVisualUtils_hpp_ +#define slic3r_VoronoiVisualUtils_hpp_ + #include #include @@ -455,3 +458,5 @@ static inline void dump_voronoi_to_svg( } } // namespace Slic3r + +#endif // slic3r_VoronoiVisualUtils_hpp_ From 4de0fdebdac5776c2b2da5c394c528975f64b729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 17 May 2024 15:08:54 +0200 Subject: [PATCH 2/4] Enable most of the Voronoi diagram test cases because most of them are correctly detected and handled. --- tests/libslic3r/test_voronoi.cpp | 91 ++++---------------------------- 1 file changed, 11 insertions(+), 80 deletions(-) diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index cd022bf616..71ef1588e7 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -4,11 +4,10 @@ #include #include #include -#include #include -// #define VORONOI_DEBUG_OUT +//#define VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT #include @@ -337,7 +336,7 @@ TEST_CASE("Voronoi division by zero 12903", "[Voronoi]") // Funny sample from a dental industry? // Vojtech confirms this test fails and rightly so, because the input data contain self intersections. // This test is suppressed. -TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]") +TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi]") { Lines lines = { { { 260500,1564400 }, { 261040,1562960 } }, @@ -1921,23 +1920,6 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]") REQUIRE(! skeleton_edges.empty()); } -// Simple detection with complexity N^2 if there is any point in the input polygons that doesn't have Voronoi vertex. -[[maybe_unused]] static bool has_missing_voronoi_vertices(const Polygons &polygons, const VD &vd) -{ - auto are_equal = [](const VD::vertex_type v, const Point &p) { return (Vec2d(v.x(), v.y()) - p.cast()).norm() <= SCALED_EPSILON; }; - - Points poly_points = to_points(polygons); - std::vector found_vertices(poly_points.size()); - for (const Point &point : poly_points) - for (const auto &vertex : vd.vertices()) - if (are_equal(vertex, point)) { - found_vertices[&point - &poly_points.front()] = true; - break; - } - - return std::find(found_vertices.begin(), found_vertices.end(), false) != found_vertices.end(); -} - // This case is composed of one square polygon, and one of the edges is divided into two parts by a point that lies on this edge. // In some applications, this point is unnecessary and can be removed (merge two parts to one edge). But for the case of // multi-material segmentation, these points are necessary. In this case, Voronoi vertex for the point, which divides the edge @@ -1952,12 +1934,9 @@ TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]") {-25000000, 25000000}, {-25000000, -25000000}, {-12412500, -25000000}, -// {- 1650000, -25000000}, { 25000000, -25000000} }; -// poly.rotate(PI / 6); - REQUIRE(poly.area() > 0.); REQUIRE(intersecting_edges({poly}).empty()); @@ -1968,7 +1947,7 @@ TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]") dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex1-out.svg").c_str(), vd, Points(), lines); #endif -// REQUIRE(!has_missing_voronoi_vertices({poly}, vd)); + REQUIRE(vd.is_valid()); } // This case is composed of two square polygons (contour and hole), and again one of the edges is divided into two parts by a @@ -1995,8 +1974,6 @@ TEST_CASE("Voronoi missing vertex 2", "[VoronoiMissingVertex2]") } }; -// polygons_rotate(poly, PI / 6); - double area = std::accumulate(poly.begin(), poly.end(), 0., [](double a, auto &poly) { return a + poly.area(); }); REQUIRE(area > 0.); REQUIRE(intersecting_edges(poly).empty()); @@ -2008,7 +1985,7 @@ TEST_CASE("Voronoi missing vertex 2", "[VoronoiMissingVertex2]") dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex2-out.svg").c_str(), vd, Points(), lines); #endif -// REQUIRE(!has_missing_voronoi_vertices(poly, vd)); + REQUIRE(vd.is_valid()); } // This case is composed of two polygons, and again one of the edges is divided into two parts by a point that lies on this edge, @@ -2039,9 +2016,6 @@ TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]") REQUIRE(area > 0.); REQUIRE(intersecting_edges(poly).empty()); - // polygons_rotate(poly, PI/180); - // polygons_rotate(poly, PI/6); - VD vd; Lines lines = to_lines(poly); vd.construct_voronoi(lines.begin(), lines.end()); @@ -2049,7 +2023,7 @@ TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]") dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex3-out.svg").c_str(), vd, Points(), lines); #endif -// REQUIRE(!has_missing_voronoi_vertices(poly, vd)); + REQUIRE(vd.is_valid()); } TEST_CASE("Voronoi missing vertex 4", "[VoronoiMissingVertex4]") @@ -2094,6 +2068,9 @@ TEST_CASE("Voronoi missing vertex 4", "[VoronoiMissingVertex4]") dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-1-out.svg").c_str(), vd_1, Points(), lines_1); dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-2-out.svg").c_str(), vd_2, Points(), lines_2); #endif + + REQUIRE(vd_1.is_valid()); + REQUIRE(vd_2.is_valid()); } // In this case, the Voronoi vertex (146873, -146873) is included twice. @@ -2114,8 +2091,6 @@ TEST_CASE("Duplicate Voronoi vertices", "[Voronoi]") { 25000000, 10790627}, }; -// poly.rotate(PI / 6); - REQUIRE(poly.area() > 0.); REQUIRE(intersecting_edges({poly}).empty()); @@ -2126,16 +2101,7 @@ TEST_CASE("Duplicate Voronoi vertices", "[Voronoi]") dump_voronoi_to_svg(debug_out_path("voronoi-duplicate-vertices-out.svg").c_str(), vd, Points(), lines); #endif - [[maybe_unused]] auto has_duplicate_vertices = [](const VD &vd) -> bool { - std::vector vertices; - for (const auto &vertex : vd.vertices()) - vertices.emplace_back(Vec2d(vertex.x(), vertex.y())); - - std::sort(vertices.begin(), vertices.end(), [](const Vec2d &l, const Vec2d &r) { return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y()); }); - return std::unique(vertices.begin(), vertices.end()) != vertices.end(); - }; - -// REQUIRE(!has_duplicate_vertices(vd)); + REQUIRE(vd.is_valid()); } // In this case, there are three very close Voronoi vertices like in the previous test case after rotation. There is also one @@ -2154,8 +2120,6 @@ TEST_CASE("Intersecting Voronoi edges", "[Voronoi]") { 25000000, - 146873}, }; -// poly.rotate(PI / 6); - REQUIRE(poly.area() > 0.); REQUIRE(intersecting_edges({poly}).empty()); @@ -2166,38 +2130,7 @@ TEST_CASE("Intersecting Voronoi edges", "[Voronoi]") dump_voronoi_to_svg(debug_out_path("voronoi-intersecting-edges-out.svg").c_str(), vd, Points(), lines); #endif - [[maybe_unused]] auto has_intersecting_edges = [](const Polygon &poly, const VD &vd) -> bool { - BoundingBox bbox = get_extents(poly); - const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); - - std::vector segments; - for (const Line &line : to_lines(poly)) - segments.emplace_back(Voronoi::Internal::point_type(double(line.a.x()), double(line.a.y())), - Voronoi::Internal::point_type(double(line.b.x()), double(line.b.y()))); - - Lines edges; - for (const auto &edge : vd.edges()) - if (edge.cell()->source_index() < edge.twin()->cell()->source_index()) { - if (edge.is_finite()) { - edges.emplace_back(Point(coord_t(edge.vertex0()->x()), coord_t(edge.vertex0()->y())), - Point(coord_t(edge.vertex1()->x()), coord_t(edge.vertex1()->y()))); - } else if (edge.is_infinite()) { - std::vector samples; - Voronoi::Internal::clip_infinite_edge(poly.points, segments, edge, bbox_dim_max, &samples); - if (!samples.empty()) - edges.emplace_back(Point(coord_t(samples[0].x()), coord_t(samples[0].y())), Point(coord_t(samples[1].x()), coord_t(samples[1].y()))); - } - } - - Point intersect_point; - for (auto first_it = edges.begin(); first_it != edges.end(); ++first_it) - for (auto second_it = first_it + 1; second_it != edges.end(); ++second_it) - if (first_it->intersection(*second_it, &intersect_point) && first_it->a != intersect_point && first_it->b != intersect_point) - return true; - return false; - }; - -// REQUIRE(!has_intersecting_edges(poly, vd)); + REQUIRE(vd.is_valid()); } // In this case resulting Voronoi diagram is not planar. This case was distilled from GH issue #8474. @@ -2216,8 +2149,6 @@ TEST_CASE("Non-planar voronoi diagram", "[VoronoiNonPlanar]") { 5500000, 40000000}, }; -// poly.rotate(PI / 6); - REQUIRE(poly.area() > 0.); REQUIRE(intersecting_edges({poly}).empty()); @@ -2228,7 +2159,7 @@ TEST_CASE("Non-planar voronoi diagram", "[VoronoiNonPlanar]") dump_voronoi_to_svg(debug_out_path("voronoi-non-planar-out.svg").c_str(), vd, Points(), lines); #endif -// REQUIRE(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(vd)); + REQUIRE(vd.is_valid()); } // This case is extracted from SPE-1729, where several ExPolygon with very thin lines From c44ffed475f55f5203142c20005568b497e6362d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 17 May 2024 15:15:34 +0200 Subject: [PATCH 3/4] SPE-2298: Add detection of Voronoi diagram with parabolic edge without a focus point. --- src/libslic3r/Geometry/Voronoi.cpp | 18 ++++++--- src/libslic3r/Geometry/Voronoi.hpp | 8 +++- tests/libslic3r/test_voronoi.cpp | 65 ++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/Geometry/Voronoi.cpp b/src/libslic3r/Geometry/Voronoi.cpp index 947c56a707..71c67b9cc1 100644 --- a/src/libslic3r/Geometry/Voronoi.cpp +++ b/src/libslic3r/Geometry/Voronoi.cpp @@ -35,6 +35,8 @@ VoronoiDiagram::construct_voronoi(const SegmentIterator segment_begin, const Seg BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth."; } else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) { BOOST_LOG_TRIVIAL(warning) << "Detected finite Voronoi vertex with non finite vertex, input polygons will be rotated back and forth."; + } else if (m_issue_type == IssueType::PARABOLIC_VORONOI_EDGE_WITHOUT_FOCUS_POINT) { + BOOST_LOG_TRIVIAL(warning) << "Detected parabolic Voronoi edges without focus point, input polygons will be rotated back and forth."; } else { BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue, input polygons will be rotated back and forth."; } @@ -48,6 +50,8 @@ VoronoiDiagram::construct_voronoi(const SegmentIterator segment_begin, const Seg BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input."; } else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) { BOOST_LOG_TRIVIAL(error) << "Detected finite Voronoi vertex with non finite vertex even after the rotation of input."; + } else if (m_issue_type == IssueType::PARABOLIC_VORONOI_EDGE_WITHOUT_FOCUS_POINT) { + BOOST_LOG_TRIVIAL(error) << "Detected parabolic Voronoi edges without focus point even after the rotation of input."; } else { BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue even after the rotation of input."; } @@ -159,8 +163,8 @@ typename boost::polygon::enable_if< VoronoiDiagram::IssueType>::type VoronoiDiagram::detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end) { - if (has_finite_edge_with_non_finite_vertex(voronoi_diagram)) { - return IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX; + if (const IssueType edge_issue_type = detect_known_voronoi_edge_issues(voronoi_diagram); edge_issue_type != IssueType::NO_ISSUE_DETECTED) { + return edge_issue_type; } else if (const IssueType cell_issue_type = detect_known_voronoi_cell_issues(voronoi_diagram, segment_begin, segment_end); cell_issue_type != IssueType::NO_ISSUE_DETECTED) { return cell_issue_type; } else if (!VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segment_begin, segment_end)) { @@ -218,16 +222,20 @@ VoronoiDiagram::detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_d return IssueType::NO_ISSUE_DETECTED; } -bool VoronoiDiagram::has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram) +VoronoiDiagram::IssueType VoronoiDiagram::detect_known_voronoi_edge_issues(const VoronoiDiagram &voronoi_diagram) { for (const voronoi_diagram_type::edge_type &edge : voronoi_diagram.edges()) { if (edge.is_finite()) { assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr); if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) || !VoronoiUtils::is_finite(*edge.vertex1())) - return true; + return IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX; + + if (edge.is_curved() && !edge.cell()->contains_point() && !edge.twin()->cell()->contains_point()) + return IssueType::PARABOLIC_VORONOI_EDGE_WITHOUT_FOCUS_POINT; } } - return false; + + return IssueType::NO_ISSUE_DETECTED; } template diff --git a/src/libslic3r/Geometry/Voronoi.hpp b/src/libslic3r/Geometry/Voronoi.hpp index 1c5d68acd0..72fa387e18 100644 --- a/src/libslic3r/Geometry/Voronoi.hpp +++ b/src/libslic3r/Geometry/Voronoi.hpp @@ -48,7 +48,8 @@ public: MISSING_VORONOI_VERTEX, NON_PLANAR_VORONOI_DIAGRAM, VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT, - UNKNOWN // Repairs are disabled in the constructor. + PARABOLIC_VORONOI_EDGE_WITHOUT_FOCUS_POINT, + UNKNOWN // Repairs are disabled in the constructor. }; enum class State { @@ -162,7 +163,10 @@ private: IssueType>::type detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end); - static bool has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram); + // Detect issues related to Voronoi edges, or that can be detected by iterating over Voronoi edges. + // The first type of issue that can be detected is a finite Voronoi edge with a non-finite vertex. + // The second type of issue that can be detected is a parabolic Voronoi edge without a focus point (produced by two segments). + static IssueType detect_known_voronoi_edge_issues(const VoronoiDiagram &voronoi_diagram); voronoi_diagram_type m_voronoi_diagram; vertex_container_type m_vertices; diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index 71ef1588e7..f415dc74c0 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -2217,3 +2217,68 @@ TEST_CASE("Invalid Voronoi diagram - Thin lines - SPE-1729", "[InvalidVoronoiDia // REQUIRE(vd.is_valid()); } + +TEST_CASE("Voronoi cell doesn't contain a source point - SPE-2298", "[VoronoiCellSourcePointSPE2298]") +{ + Polygon polygon = { + { 9854534, -39739718}, {- 4154002, -34864557}, {-13073118, -31802214}, + {-21265508, -29026626}, {-31388055, -25645073}, {-32409943, -25279942}, + {-33418087, -24864987}, {-34400568, -24404312}, {-35354754, -23899358}, + {-36278795, -23351325}, {-37170015, -22762146}, {-38025776, -22134628}, + {-38845825, -21468175}, {-39627905, -20764801}, {-40370549, -20026061}, + {-41072075, -19253859}, {-41731000, -18450032}, {-42345940, -17616466}, + {-42915530, -16755283}, {-43438684, -15868338}, {-43914245, -14957822}, + {-44341235, -14025879}, {-44718686, -13074712}, {-45045890, -12106566}, + {-45322386, -11123499}, {-45547674, -10127656}, {-45721021, - 9121581}, + {-45842174, - 8107658}, {-45910990, - 7088089}, {-45927405, - 6065217}, + {-45891432, - 5041288}, {-45803222, - 4018728}, {-45663042, - 2999801}, + {-45471245, - 1986784}, {-45230690, - 991761}, {-38655400, 23513180}, + {-38366034, 24494742}, {-38025334, 25467666}, {-37636844, 26420074}, + {-37200678, 27349843}, {-36718169, 28254419}, {-36191104, 29131704}, + {-35620587, 29979748}, {-35007621, 30796895}, {-34353751, 31580950}, + {-33660293, 32330182}, {-32928806, 33042775}, {-32160862, 33717057}, + {-31358104, 34351432}, {-30522331, 34944323}, {-29655434, 35494277}, + {-28759338, 35999922}, {-27835963, 36460011}, {-26921721, 36858494}, + {-25914008, 37239556}, {-24919466, 37557049}, {-24204878, 37746930}, + {-22880526, 38041931}, {-21833362, 38209050}, {-21449204, 38252031}, + {-20775657, 38324377}, {-19711119, 38387480}, {-18638667, 38398812}, + {-17762260, 38366962}, {-16480321, 38266321}, {-15396213, 38120856}, + {-14327987, 37925343}, {- 5801522, 36175494}, { 7791637, 33457589}, + { 15887399, 31878986}, { 28428609, 29478881}, { 28438392, 29512722}, + { 27850323, 29743358}, { 27058729, 29970066}, { 14135560, 32452875}, + { 6101685, 34019760}, {- 5352362, 36305237}, {-14423391, 38160442}, + {-15528705, 38361745}, {-16625379, 38507834}, {-17721787, 38600631}, + {-18812787, 38641330}, {-19563804, 38633844}, {-20975692, 38563412}, + {-22036069, 38446419}, {-23087710, 38277136}, {-24123993, 38056689}, + {-25141240, 37786307}, {-26138324, 37466465}, {-26851801, 37197652}, + {-28067514, 36680229}, {-28988984, 36219404}, {-29886302, 35711371}, + {-30754551, 35158840}, {-31124518, 34896643}, {-31589528, 34564743}, + {-32392776, 33928220}, {-33161225, 33251721}, {-33454722, 32966117}, + {-33891684, 32538320}, {-34585318, 31787066}, {-35239508, 31000793}, + {-35527715, 30616853}, {-35851756, 30182731}, {-36422833, 29331982}, + {-36950377, 28452000}, {-37265788, 27860633}, {-37432874, 27545549}, + {-37870512, 26612217}, {-38261423, 25655915}, {-38581885, 24744387}, + {-38902507, 23671594}, {-45523689, - 1006672}, {-45770290, - 2026713}, + {-45963584, - 3043930}, {-46104330, - 4066310}, {-46192377, - 5092635}, + {-46228310, - 6120019}, {-46211987, - 7145807}, {-46143426, - 8167812}, + {-46022719, - 9183674}, {-45850055, -10191399}, {-45625772, -11188531}, + {-45350245, -12172975}, {-45023965, -13142600}, {-44647538, -14095222}, + {-44221691, -15028602}, {-43747176, -15940794}, {-43224933, -16829570}, + {-42655872, -17693052}, {-42041183, -18529065}, {-41381752, -19335983}, + {-40677899, -20112975}, {-39932077, -20856972}, {-39145730, -21566171}, + {-38320552, -22238686}, {-37458030, -22872953}, {-36560036, -23467217}, + {-35627745, -24020614}, {-34662272, -24532977}, {-33667551, -25000722}, + {-32645434, -25422669}, {-31588226, -25801077}, {-24380013, -28208306}, + {-24380013, -28208306}, {-13354262, -31942517}, {-13354261, -31942515}, + {-2032305, -35842454}, { 8025116, -39348505}, { 8820397, -39587703}, + { 9636283, -39751794}, { 9847092, -39773278}}; + + VD vd; + Lines lines = to_lines(polygon); + vd.construct_voronoi(lines.begin(), lines.end()); +#ifdef VORONOI_DEBUG_OUT +// dump_voronoi_to_svg(debug_out_path("voronoi-cell-source-point-spe2298.svg").c_str(), vd, Points(), lines); +#endif + + REQUIRE(vd.is_valid()); +} From 669c931b7743ef74dd005819449b27a1544018c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 23 May 2024 10:41:58 +0200 Subject: [PATCH 4/4] SPE-2298: Fix crash caused by a numerical issue during testing if a Voronoi vertex is inside a corner of a polygon. --- .../Arachne/SkeletalTrapezoidation.cpp | 60 +++----------- .../Arachne/SkeletalTrapezoidation.hpp | 28 +------ .../Arachne/SkeletalTrapezoidationGraph.cpp | 3 +- .../Arachne/SkeletalTrapezoidationGraph.hpp | 2 +- src/libslic3r/Arachne/utils/linearAlg2D.hpp | 53 ------------ src/libslic3r/Geometry.cpp | 50 ++++++++++- src/libslic3r/Geometry.hpp | 22 ++++- src/libslic3r/Geometry/VoronoiUtils.cpp | 58 +++++++++++++ src/libslic3r/Geometry/VoronoiUtils.hpp | 54 ++++++++++-- tests/libslic3r/test_arachne.cpp | 83 +++++++++++++++++++ 10 files changed, 267 insertions(+), 146 deletions(-) diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index c80e70e61c..ee0182a7cf 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include "utils/linearAlg2D.hpp" @@ -104,7 +103,7 @@ SkeletalTrapezoidation::node_t &SkeletalTrapezoidation::makeNode(const VD::verte } } -void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector &segments) { +void SkeletalTrapezoidation::transferEdge(const Point &from, const Point &to, const VD::edge_type &vd_edge, edge_t *&prev_edge, const Point &start_source_point, const Point &end_source_point, const std::vector &segments) { auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin()); if (he_edge_it != vd_edge_to_he_edge.end()) { // Twin segment(s) have already been made @@ -323,50 +322,6 @@ Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const st } } -bool SkeletalTrapezoidation::computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector &segments) { - if (cell.incident_edge()->is_infinite()) - return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell. - - // Check if any point of the cell is inside or outside polygon - // Copy whole cell into graph or not at all - - // If the cell.incident_edge()->vertex0() is far away so much that it doesn't even fit into Vec2i64, then there is no way that it will be inside the input polygon. - if (const VD::vertex_type &vert = *cell.incident_edge()->vertex0(); - vert.x() >= double(std::numeric_limits::max()) || vert.x() <= double(std::numeric_limits::lowest()) || - vert.y() >= double(std::numeric_limits::max()) || vert.y() <= double(std::numeric_limits::lowest())) - return false; // Don't copy any part of this cell - - const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segments.begin(), segments.end()); - const PolygonsPointIndex source_point_index = Geometry::VoronoiUtils::get_source_point_index(cell, segments.begin(), segments.end()); - Vec2i64 some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex0()); - if (some_point == source_point.cast()) - some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex1()); - - //Test if the some_point is even inside the polygon. - //The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex. - //So if it's inside the corner formed by the polygon vertex, it's all fine. - //But if it's outside of the corner, it must be a vertex of the Voronoi diagram that goes outside of the polygon towards infinity. - if (!LinearAlg2D::isInsideCorner(source_point_index.prev().p(), source_point_index.p(), source_point_index.next().p(), some_point)) - return false; // Don't copy any part of this cell - - const VD::edge_type* vd_edge = cell.incident_edge(); - do { - assert(vd_edge->is_finite()); - if (Vec2i64 p1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()); p1 == source_point.cast()) { - start_source_point = source_point; - end_source_point = source_point; - starting_vd_edge = vd_edge->next(); - ending_vd_edge = vd_edge; - } else { - assert((Geometry::VoronoiUtils::to_point(vd_edge->vertex0()) == source_point.cast() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input."); - } - } - while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge()); - assert(starting_vd_edge && ending_vd_edge); - assert(starting_vd_edge != ending_vd_edge); - return true; -} - SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy, double transitioning_angle, coord_t discretization_step_size, coord_t transition_filter_dist, coord_t allowed_filter_deviation, @@ -434,15 +389,20 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) // Compute and store result in above variables if (cell.contains_point()) { - const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments); - if (!keep_going) + Geometry::PointCellRange cell_range = Geometry::VoronoiUtils::compute_point_cell_range(cell, segments.cbegin(), segments.cend()); + start_source_point = cell_range.source_point; + end_source_point = cell_range.source_point; + starting_voronoi_edge = cell_range.edge_begin; + ending_voronoi_edge = cell_range.edge_end; + + if (!cell_range.is_valid()) continue; } else { assert(cell.contains_segment()); Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.cbegin(), segments.cend()); assert(cell_range.is_valid()); - start_source_point = cell_range.segment_start_point; - end_source_point = cell_range.segment_end_point; + start_source_point = cell_range.source_segment_start_point; + end_source_point = cell_range.source_segment_end_point; starting_voronoi_edge = cell_range.edge_begin; ending_voronoi_edge = cell_range.edge_end; } diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index b4c481263b..a728d7b03d 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -177,7 +177,7 @@ protected: * Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges) * \p prev_edge serves as input and output. May be null as input. */ - void transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector &segments); + void transferEdge(const Point &from, const Point &to, const VD::edge_type &vd_edge, edge_t *&prev_edge, const Point &start_source_point, const Point &end_source_point, const std::vector &segments); /*! * Discretize a Voronoi edge that represents the medial axis of a vertex- @@ -206,32 +206,6 @@ protected: */ Points discretize(const VD::edge_type& segment, const std::vector& segments); - /*! - * Compute the range of line segments that surround a cell of the skeletal - * graph that belongs to a point on the medial axis. - * - * This should only be used on cells that belong to a corner in the skeletal - * graph, e.g. triangular cells, not trapezoid cells. - * - * The resulting line segments is just the first and the last segment. They - * are linked to the neighboring segments, so you can iterate over the - * segments until you reach the last segment. - * \param cell The cell to compute the range of line segments for. - * \param[out] start_source_point The start point of the source segment of - * this cell. - * \param[out] end_source_point The end point of the source segment of this - * cell. - * \param[out] starting_vd_edge The edge of the Voronoi diagram where the - * loop around the cell starts. - * \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop - * around the cell ends. - * \param points All vertices of the input Polygons. - * \param segments All edges of the input Polygons. - * /return Whether the cell is inside of the polygon. If it's outside of the - * polygon we should skip processing it altogether. - */ - static bool computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector &segments); - /*! * For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two * That way we can reach both the quad_start and the quad_end from the [incident_edge] of the two new nodes diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp index 0f97b23db9..97932c5abb 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp @@ -314,8 +314,7 @@ void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist) } } -void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point) -{ +void SkeletalTrapezoidationGraph::makeRib(edge_t *&prev_edge, const Point &start_source_point, const Point &end_source_point) { Point p; Line(start_source_point, end_source_point).distance_to_infinite_squared(prev_edge->to->p, &p); coord_t dist = (prev_edge->to->p - p).cast().norm(); diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp index 41c681f3e7..ffd1edb55b 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp @@ -88,7 +88,7 @@ public: */ void collapseSmallEdges(coord_t snap_dist = 5); - void makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point); + void makeRib(edge_t*& prev_edge, const Point &start_source_point, const Point &end_source_point); /*! * Insert a node into the graph and connect it to the input polygon using ribs diff --git a/src/libslic3r/Arachne/utils/linearAlg2D.hpp b/src/libslic3r/Arachne/utils/linearAlg2D.hpp index 304984b1ca..7ece14c24c 100644 --- a/src/libslic3r/Arachne/utils/linearAlg2D.hpp +++ b/src/libslic3r/Arachne/utils/linearAlg2D.hpp @@ -9,59 +9,6 @@ namespace Slic3r::Arachne::LinearAlg2D { -/*! - * Test whether a point is inside a corner. - * Whether point \p query_point is left of the corner abc. - * Whether the \p query_point is in the circle half left of ab and left of bc, rather than to the right. - * - * Test whether the \p query_point is inside of a polygon w.r.t a single corner. - */ -inline static bool isInsideCorner(const Point &a, const Point &b, const Point &c, const Vec2i64 &query_point) -{ - // Visualisation for the algorithm below: - // - // query - // | - // | - // | - // perp-----------b - // / \ (note that the lines - // / \ AB and AC are normalized - // / \ to 10000 units length) - // a c - // - - auto normal = [](const Point &p0, coord_t len) -> Point { - int64_t _len = p0.cast().norm(); - if (_len < 1) - return {len, 0}; - return (p0.cast() * int64_t(len) / _len).cast(); - }; - - constexpr coord_t normal_length = 10000; //Create a normal vector of reasonable length in order to reduce rounding error. - const Point ba = normal(a - b, normal_length); - const Point bc = normal(c - b, normal_length); - const Vec2d bq = query_point.cast() - b.cast(); - const Vec2d perpendicular = perp(bq); //The query projects to this perpendicular to coordinate 0. - - const double project_a_perpendicular = ba.cast().dot(perpendicular); //Project vertex A on the perpendicular line. - const double project_c_perpendicular = bc.cast().dot(perpendicular); //Project vertex C on the perpendicular line. - if ((project_a_perpendicular > 0.) != (project_c_perpendicular > 0.)) //Query is between A and C on the projection. - { - return project_a_perpendicular > 0.; //Due to the winding order of corner ABC, this means that the query is inside. - } - else //Beyond either A or C, but it could still be inside of the polygon. - { - const double project_a_parallel = ba.cast().dot(bq); //Project not on the perpendicular, but on the original. - const double project_c_parallel = bc.cast().dot(bq); - - //Either: - // * A is to the right of B (project_a_perpendicular > 0) and C is below A (project_c_parallel < project_a_parallel), or - // * A is to the left of B (project_a_perpendicular < 0) and C is above A (project_c_parallel > project_a_parallel). - return (project_c_parallel < project_a_parallel) == (project_a_perpendicular > 0.); - } -} - /*! * Returns the determinant of the 2D matrix defined by the the vectors ab and ap as rows. * diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 810d7ef4b9..236a187381 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -38,7 +38,7 @@ #define BOOST_NO_CXX17_HDR_STRING_VIEW #endif -namespace Slic3r { namespace Geometry { +namespace Slic3r::Geometry { bool directions_parallel(double angle1, double angle2, double max_diff) { @@ -775,4 +775,50 @@ bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d & return std::abs(d * d) < EPSILON * lx2 * ly2; } -}} // namespace Slic3r::Geometry +bool is_point_inside_polygon_corner(const Point &a, const Point &b, const Point &c, const Point &query_point) { + // Cast all input points into int64_t to prevent overflows when points are close to max values of coord_t. + const Vec2i64 a_i64 = a.cast(); + const Vec2i64 b_i64 = b.cast(); + const Vec2i64 c_i64 = c.cast(); + const Vec2i64 query_point_i64 = query_point.cast(); + + // Shift all points to have a base in vertex B. + // Then construct normalized vectors to ensure that we will work with vectors with endpoints on the unit circle. + const Vec2d ba = (a_i64 - b_i64).cast().normalized(); + const Vec2d bc = (c_i64 - b_i64).cast().normalized(); + const Vec2d bq = (query_point_i64 - b_i64).cast().normalized(); + + // Points A and C has to be different. + assert(ba != bc); + + // Construct a normal for the vector BQ that points to the left side of the vector BQ. + const Vec2d bq_left_normal = perp(bq); + + const double proj_a_on_bq_normal = ba.dot(bq_left_normal); // Project point A on the normal of BQ. + const double proj_c_on_bq_normal = bc.dot(bq_left_normal); // Project point C on the normal of BQ. + if ((proj_a_on_bq_normal > 0. && proj_c_on_bq_normal <= 0.) || (proj_a_on_bq_normal <= 0. && proj_c_on_bq_normal > 0.)) { + // Q is between points A and C or lies on one of those vectors (BA or BC). + + // Based on the CCW order of polygons (contours) and order of corner ABC, + // when this condition is met, the query point is inside the corner. + return proj_a_on_bq_normal > 0.; + } else { + // Q isn't between points A and C, but still it can be inside the corner. + + const double proj_a_on_bq = ba.dot(bq); // Project point A on BQ. + const double proj_c_on_bq = bc.dot(bq); // Project point C on BQ. + + // The value of proj_a_on_bq_normal is the same when we project the vector BA on the normal of BQ. + // So we can say that the Q is on the right side of the vector BA when proj_a_on_bq_normal > 0, and + // that the Q is on the left side of the vector BA proj_a_on_bq_normal < 0. + // Also, the Q is on the right side of the bisector of oriented angle ABC when proj_c_on_bq < proj_a_on_bq, and + // the Q is on the left side of the bisector of oriented angle ABC when proj_c_on_bq > proj_a_on_bq. + + // So the Q is inside the corner when one of the following conditions is met: + // * The Q is on the right side of the vector BA, and the Q is on the right side of the bisector of the oriented angle ABC. + // * The Q is on the left side of the vector BA, and the Q is on the left side of the bisector of the oriented angle ABC. + return (proj_a_on_bq_normal > 0. && proj_c_on_bq < proj_a_on_bq) || (proj_a_on_bq_normal <= 0. && proj_c_on_bq >= proj_a_on_bq); + } +} + +} // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 3afe6923da..ec3c317518 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -25,9 +25,7 @@ // Serialization through the Cereal library #include -namespace Slic3r { - -namespace Geometry { +namespace Slic3r::Geometry { // Generic result of an orientation predicate. enum Orientation @@ -553,6 +551,22 @@ Vec<3, T> spheric_to_dir(const Pair &v) return spheric_to_dir(plr, azm); } -} } // namespace Slicer::Geometry +/** + * Checks if a given point is inside a corner of a polygon. + * + * The corner of a polygon is defined by three points A, B, C in counterclockwise order. + * + * Adapted from CuraEngine LinearAlg2D::isInsideCorner by Tim Kuipers @BagelOrb + * and @Ghostkeeper. + * + * @param a The first point of the corner. + * @param b The second point of the corner (the common vertex of the two edges forming the corner). + * @param c The third point of the corner. + * @param query_point The point to be checked if is inside the corner. + * @return True if the query point is inside the corner, false otherwise. + */ +bool is_point_inside_polygon_corner(const Point &a, const Point &b, const Point &c, const Point &query_point); + +} // namespace Slic3r::Geometry #endif diff --git a/src/libslic3r/Geometry/VoronoiUtils.cpp b/src/libslic3r/Geometry/VoronoiUtils.cpp index 1e9436325f..968a3f3a06 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.cpp +++ b/src/libslic3r/Geometry/VoronoiUtils.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "VoronoiUtils.hpp" @@ -27,6 +28,7 @@ template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt); template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); +template PointCellRange VoronoiUtils::compute_point_cell_range(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); template Points VoronoiUtils::discretize_parabola(const Point &, const Arachne::PolygonsSegmentIndex &, const Point &, const Point &, coord_t, float); template Arachne::PolygonsPointIndex VoronoiUtils::get_source_point_index(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); @@ -244,6 +246,62 @@ VoronoiUtils::compute_segment_cell_range(const VD::cell_type &cell, const Segmen return cell_range; } +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + Geometry::PointCellRange< + typename boost::polygon::segment_point_type::value_type>::type>>::type +VoronoiUtils::compute_point_cell_range(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + using Segment = typename std::iterator_traits::value_type; + using Point = typename boost::polygon::segment_point_type::type; + using PointCellRange = PointCellRange; + using CoordType = typename Point::coord_type; + + const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segment_begin, segment_end); + + // We want to ignore (by returning PointCellRange without assigned edge_begin and edge_end) cells outside the input polygon. + PointCellRange cell_range(source_point); + + const VD::edge_type *edge = cell.incident_edge(); + if (edge->is_infinite() || !is_in_range(*edge)) { + // Ignore infinite edges, because they only occur outside the polygon. + // Also ignore edges with endpoints that don't fit into CoordType, because such edges are definitely outside the polygon. + return cell_range; + } + + const Arachne::PolygonsPointIndex source_point_idx = Geometry::VoronoiUtils::get_source_point_index(cell, segment_begin, segment_end); + const Point edge_v0 = Geometry::VoronoiUtils::to_point(edge->vertex0()).template cast(); + const Point edge_v1 = Geometry::VoronoiUtils::to_point(edge->vertex1()).template cast(); + const Point edge_query_point = (edge_v0 == source_point) ? edge_v1 : edge_v0; + + // Check if the edge has another endpoint inside the corner of the polygon. + if (!Geometry::is_point_inside_polygon_corner(source_point_idx.prev().p(), source_point_idx.p(), source_point_idx.next().p(), edge_query_point)) { + // If the endpoint isn't inside the corner of the polygon, it means that + // the whole cell isn't inside the polygons, and we will ignore such cells. + return cell_range; + } + + const Vec2i64 source_point_i64 = source_point.template cast(); + edge = cell.incident_edge(); + do { + assert(edge->is_finite()); + + if (Vec2i64 v1 = Geometry::VoronoiUtils::to_point(edge->vertex1()); v1 == source_point_i64) { + cell_range.edge_begin = edge->next(); + cell_range.edge_end = edge; + } else { + // FIXME @hejllukas: With Arachne, we don't support polygons with collinear edges, + // because with collinear edges we have to handle secondary edges. + // Such edges goes through the endpoints of the input segments. + assert((Geometry::VoronoiUtils::to_point(edge->vertex0()) == source_point_i64 || edge->is_primary()) && "Point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input."); + } + } while (edge = edge->next(), edge != cell.incident_edge()); + + return cell_range; +} + Vec2i64 VoronoiUtils::to_point(const VD::vertex_type *vertex) { assert(vertex != nullptr); diff --git a/src/libslic3r/Geometry/VoronoiUtils.hpp b/src/libslic3r/Geometry/VoronoiUtils.hpp index bf63914677..9c915e223b 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.hpp +++ b/src/libslic3r/Geometry/VoronoiUtils.hpp @@ -11,15 +11,28 @@ namespace Slic3r::Geometry { // Represent trapezoid Voronoi cell around segment. template struct SegmentCellRange { - const PT segment_start_point; // The start point of the source segment of this cell. - const PT segment_end_point; // The end point of the source segment of this cell. + const PT source_segment_start_point; // The start point of the source segment of this cell. + const PT source_segment_end_point; // The end point of the source segment of this cell. + const VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts. + const VD::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends. + + SegmentCellRange() = delete; + explicit SegmentCellRange(const PT &source_segment_start_point, const PT &source_segment_end_point) + : source_segment_start_point(source_segment_start_point), source_segment_end_point(source_segment_end_point) + {} + + bool is_valid() const { return edge_begin && edge_end && edge_begin != edge_end; } +}; + +// Represent trapezoid Voronoi cell around point. +template struct PointCellRange +{ + const PT source_point; // The source point of this cell. const VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts. const VD::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends. - SegmentCellRange() = delete; - explicit SegmentCellRange(const PT &segment_start_point, const PT &segment_end_point) - : segment_start_point(segment_start_point), segment_end_point(segment_end_point) - {} + PointCellRange() = delete; + explicit PointCellRange(const PT &source_point) : source_point(source_point) {} bool is_valid() const { return edge_begin && edge_end && edge_begin != edge_end; } }; @@ -80,7 +93,7 @@ public: * are linked to the neighboring segments, so you can iterate over the * segments until you reach the last segment. * - * Adapted from CuraEngine VoronoiUtils::computePointCellRange by Tim Kuipers @BagelOrb, + * Adapted from CuraEngine VoronoiUtils::computeSegmentCellRange by Tim Kuipers @BagelOrb, * Jaime van Kessel @nallath, Remco Burema @rburema and @Ghostkeeper. * * @param cell The cell to compute the range of line segments for. @@ -96,6 +109,33 @@ public: typename boost::polygon::segment_point_type::value_type>::type>>::type compute_segment_cell_range(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + /** + * Compute the range of line segments that surround a cell of the skeletal + * graph that belongs to a point on the medial axis. + * + * This should only be used on cells that belong to a corner in the skeletal + * graph, e.g. triangular cells, not trapezoid cells. + * + * The resulting line segments is just the first and the last segment. They + * are linked to the neighboring segments, so you can iterate over the + * segments until you reach the last segment. + * + * Adapted from CuraEngine VoronoiUtils::computePointCellRange by Tim Kuipers @BagelOrb + * Jaime van Kessel @nallath, Remco Burema @rburema and @Ghostkeeper. + * + * @param cell The cell to compute the range of line segments for. + * @param segment_begin Begin iterator for all edges of the input Polygons. + * @param segment_end End iterator for all edges of the input Polygons. + * @return Range of line segments that surround the cell. + */ + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + Geometry::PointCellRange< + typename boost::polygon::segment_point_type::value_type>::type>>::type + compute_point_cell_range(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + template static bool is_in_range(double value) { return double(std::numeric_limits::lowest()) <= value && value <= double(std::numeric_limits::max()); diff --git a/tests/libslic3r/test_arachne.cpp b/tests/libslic3r/test_arachne.cpp index 2953cfcfab..6ebdda2ec2 100644 --- a/tests/libslic3r/test_arachne.cpp +++ b/tests/libslic3r/test_arachne.cpp @@ -764,3 +764,86 @@ TEST_CASE("Arachne - SPE-1837 - No perimeters generated", "[ArachneNoPerimetersG REQUIRE(!perimeters.empty()); } + +TEST_CASE("Arachne - SPE-2298 - Missing twin edge", "[ArachneMissingTwinEdgeSPE2298]") { + Polygon poly_0 = { + Point(45275325, -26003582), + Point(46698318, -24091837), + Point(45534079, - 7648226), + Point(44427730, 6913138), + Point(42406709, 31931594), + Point(42041617, 31895427), + Point(42556409, 25628802), + Point(43129149, 18571997), + Point(44061956, 6884616), + Point(44482729, 1466404), + Point(45172290, - 7674740), + Point(46329004, -23890062), + Point(46303776, -23895512), + Point(45146815, - 7676652), + Point(44457276, 1464203), + Point(44036504, 6882422), + Point(43103702, 18569730), + Point(42015592, 31899494), + Point(41650258, 31866937), + Point(44100538, 1436619) + }; + + Polygons polygons = {poly_0}; + coord_t ext_perimeter_spacing = 407079; + coord_t perimeter_spacing = 407079; + coord_t inset_count = 1; + + Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults()); + wall_tool_paths.generate(); + std::vector perimeters = wall_tool_paths.getToolPaths(); + + REQUIRE(!perimeters.empty()); +} + +TEST_CASE("Arachne - SPE-2298 - Missing twin edge - 2", "[ArachneMissingTwinEdge2SPE2298]") { + Polygon poly_0 = { + Point(-8908308, -51405945), + Point(-12709229, -51250796), + Point(-12746335, -51233657), + Point(-12830242, -51142897), + Point(-12826443, -51134671), + Point(-13181213, -51120650), + Point(-13184646, -51206854), + Point(-19253324, -50972142), + Point(-19253413, -50972139), + Point(-20427346, -50924668), + Point(-20427431, -50924664), + Point(-25802429, -50698485), + Point(-25802568, -50698481), + Point(-28983179, -50556020), + Point(-28984425, -50555950), + Point(-29799753, -50499586), + Point(-29801136, -50499472), + Point(-29856539, -50494137), + Point(-29857834, -50493996), + Point(-30921022, -50364409), + Point(-30922312, -50364235), + Point(-31012584, -50350908), + Point(-31022222, -50358055), + Point(-31060596, -50368155), + Point(-31429495, -50322406), + Point(-31460950, -50531962), + Point(-31194587, -50578945), + Point(-30054463, -50718244), + Point(-28903516, -50799260), + Point(-14217296, -51420133), + Point(-8916965, -51624212) + }; + + Polygons polygons = {poly_0}; + coord_t ext_perimeter_spacing = 407079; + coord_t perimeter_spacing = 407079; + coord_t inset_count = 1; + + Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults()); + wall_tool_paths.generate(); + std::vector perimeters = wall_tool_paths.getToolPaths(); + + REQUIRE(!perimeters.empty()); +}