From 5f37d422f069edef5302dd1c241c8dd2e3158a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 31 Jan 2024 17:40:09 +0100 Subject: [PATCH 01/11] Switch orientation of passed into Voronoi generator, because previously were opposite. So previously, SOURCE_CATEGORY_SEGMENT_START_POINT and SOURCE_CATEGORY_SEGMENT_END_POINT were swapped. --- .../Arachne/SkeletalTrapezoidation.cpp | 19 ------------------- .../Arachne/utils/PolygonsSegmentIndex.hpp | 19 +++++++++++++++++++ src/libslic3r/Arachne/utils/VoronoiUtils.cpp | 10 ++++------ 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 26d2dbeee3..2be3ef1949 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -21,25 +21,6 @@ #define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance). -namespace boost::polygon { - -template<> struct geometry_concept -{ - typedef segment_concept type; -}; - -template<> struct segment_traits -{ - typedef coord_t coordinate_type; - typedef Slic3r::Point point_type; - static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir) - { - return dir.to_int() ? CSegment.p() : CSegment.next().p(); - } -}; - -} // namespace boost::polygon - namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp b/src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp index 6eff3d62ee..3258b41c7d 100644 --- a/src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp +++ b/src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp @@ -27,5 +27,24 @@ public: } // namespace Slic3r::Arachne +namespace boost::polygon { + +template<> struct geometry_concept +{ + typedef segment_concept type; +}; + +template<> struct segment_traits +{ + typedef coord_t coordinate_type; + typedef Slic3r::Point point_type; + + static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir) + { + return dir.to_int() ? CSegment.to() : CSegment.from(); + } +}; + +} // namespace boost::polygon #endif//UTILS_POLYGONS_SEGMENT_INDEX_H diff --git a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp index 675a0ebb42..033657e5a8 100644 --- a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp +++ b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp @@ -34,11 +34,11 @@ Point VoronoiUtils::getSourcePoint(const vd_t::cell_type& cell, const std::vecto break; case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: assert(cell.source_index() < segments.size()); - return segments[cell.source_index()].to(); + return boost::polygon::segment_traits::get(segments[cell.source_index()], boost::polygon::LOW); break; case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: assert(cell.source_index() < segments.size()); - return segments[cell.source_index()].from(); + return boost::polygon::segment_traits::get(segments[cell.source_index()], boost::polygon::HIGH); break; default: assert(false && "getSourcePoint should only be called on point cells!\n"); @@ -60,14 +60,12 @@ PolygonsPointIndex VoronoiUtils::getSourcePointIndex(const vd_t::cell_type& cell switch (cell.source_category()) { case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: { assert(cell.source_index() < segments.size()); - PolygonsPointIndex ret = segments[cell.source_index()]; - ++ret; - return ret; + return segments[cell.source_index()]; break; } case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: { assert(cell.source_index() < segments.size()); - return segments[cell.source_index()]; + return segments[cell.source_index()].next(); break; } default: From 4b6a6379fc33991b66820a8cac987b16189428b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 31 Jan 2024 17:40:33 +0100 Subject: [PATCH 02/11] Refactoring of VoronoiUtils::discretizeParabola() to remove PointMatrix. --- src/libslic3r/Arachne/utils/VoronoiUtils.cpp | 172 ++++++------------- src/libslic3r/Arachne/utils/VoronoiUtils.hpp | 2 +- src/libslic3r/CMakeLists.txt | 3 +- src/libslic3r/Geometry/VoronoiUtils.cpp | 10 ++ src/libslic3r/Geometry/VoronoiUtils.hpp | 36 ++++ 5 files changed, 101 insertions(+), 122 deletions(-) create mode 100644 src/libslic3r/Geometry/VoronoiUtils.cpp create mode 100644 src/libslic3r/Geometry/VoronoiUtils.hpp diff --git a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp index 033657e5a8..f06c15da64 100644 --- a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp +++ b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp @@ -7,6 +7,7 @@ #include "linearAlg2D.hpp" #include "VoronoiUtils.hpp" +#include "libslic3r/Geometry/VoronoiUtils.hpp" namespace Slic3r::Arachne { @@ -85,164 +86,95 @@ const VoronoiUtils::Segment &VoronoiUtils::getSourceSegment(const vd_t::cell_typ return segments[cell.source_index()]; } -class PointMatrix -{ -public: - double matrix[4]; - - PointMatrix() - { - matrix[0] = 1; - matrix[1] = 0; - matrix[2] = 0; - matrix[3] = 1; - } - - PointMatrix(double rotation) - { - rotation = rotation / 180 * M_PI; - matrix[0] = cos(rotation); - matrix[1] = -sin(rotation); - matrix[2] = -matrix[1]; - matrix[3] = matrix[0]; - } - - PointMatrix(const Point p) - { - matrix[0] = p.x(); - matrix[1] = p.y(); - double f = sqrt((matrix[0] * matrix[0]) + (matrix[1] * matrix[1])); - matrix[0] /= f; - matrix[1] /= f; - matrix[2] = -matrix[1]; - matrix[3] = matrix[0]; - } - - static PointMatrix scale(double s) - { - PointMatrix ret; - ret.matrix[0] = s; - ret.matrix[3] = s; - return ret; - } - - Point apply(const Point p) const - { - return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[1]), coord_t(p.x() * matrix[2] + p.y() * matrix[3])); - } - - Point unapply(const Point p) const - { - return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[2]), coord_t(p.x() * matrix[1] + p.y() * matrix[3])); - } -}; -Points VoronoiUtils::discretizeParabola(const Point& p, const Segment& segment, Point s, Point e, coord_t approximate_step_size, float transitioning_angle) +Points VoronoiUtils::discretizeParabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, const coord_t approximate_step_size, float transitioning_angle) { Points discretized; // x is distance of point projected on the segment ab // xx is point projected on the segment ab - const Point a = segment.from(); - const Point b = segment.to(); - const Point ab = b - a; - const Point as = s - a; - const Point ae = e - a; + const Point a = source_segment.from(); + const Point b = source_segment.to(); + const Point ab = b - a; + const Point as = start - a; + const Point ae = end - a; const coord_t ab_size = ab.cast().norm(); - const coord_t sx = as.cast().dot(ab.cast()) / ab_size; - const coord_t ex = ae.cast().dot(ab.cast()) / ab_size; - const coord_t sxex = ex - sx; + const coord_t sx = as.cast().dot(ab.cast()) / ab_size; + const coord_t ex = ae.cast().dot(ab.cast()) / ab_size; + const coord_t sxex = ex - sx; - assert((as.cast().dot(ab.cast()) / int64_t(ab_size)) <= std::numeric_limits::max()); - assert((ae.cast().dot(ab.cast()) / int64_t(ab_size)) <= std::numeric_limits::max()); - - const Point ap = p - a; + const Point ap = source_point - a; const coord_t px = ap.cast().dot(ab.cast()) / ab_size; - assert((ap.cast().dot(ab.cast()) / int64_t(ab_size)) <= std::numeric_limits::max()); - Point pxx; - Line(a, b).distance_to_infinite_squared(p, &pxx); - const Point ppxx = pxx - p; - const coord_t d = ppxx.cast().norm(); - const PointMatrix rot = PointMatrix(perp(ppxx)); + Line(a, b).distance_to_infinite_squared(source_point, &pxx); + const Point ppxx = pxx - source_point; + const coord_t d = ppxx.cast().norm(); - if (d == 0) - { - discretized.emplace_back(s); - discretized.emplace_back(e); + const Vec2d rot = perp(ppxx).cast().normalized(); + const double rot_cos_theta = rot.x(); + const double rot_sin_theta = rot.y(); + + if (d == 0) { + discretized.emplace_back(start); + discretized.emplace_back(end); return discretized; } - - const float marking_bound = atan(transitioning_angle * 0.5); - int64_t msx = - marking_bound * int64_t(d); // projected marking_start - int64_t mex = marking_bound * int64_t(d); // projected marking_end - assert(msx <= std::numeric_limits::max()); - assert(double(msx) * double(msx) <= double(std::numeric_limits::max())); - assert(mex <= std::numeric_limits::max()); - assert(double(msx) * double(msx) / double(2 * d) + double(d / 2) <= std::numeric_limits::max()); + const double marking_bound = atan(transitioning_angle * 0.5); + int64_t msx = -marking_bound * int64_t(d); // projected marking_start + int64_t mex = marking_bound * int64_t(d); // projected marking_end const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2; - Point marking_start = rot.unapply(Point(coord_t(msx), marking_start_end_h)) + pxx; - Point marking_end = rot.unapply(Point(coord_t(mex), marking_start_end_h)) + pxx; - const int dir = (sx > ex) ? -1 : 1; - if (dir < 0) - { + Point marking_start = Point(coord_t(msx), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx; + Point marking_end = Point(coord_t(mex), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx; + const int dir = (sx > ex) ? -1 : 1; + if (dir < 0) { std::swap(marking_start, marking_end); std::swap(msx, mex); } - + bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir); - bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir); + bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir); - const Point apex = rot.unapply(Point(0, d / 2)) + pxx; - bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0; + const Point apex = Point(0, d / 2).rotated(rot_cos_theta, rot_sin_theta) + pxx; + bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0; - assert(!(add_marking_start && add_marking_end) || add_apex); - if(add_marking_start && add_marking_end && !add_apex) - { + assert(!add_marking_start || !add_marking_end || add_apex); + if (add_marking_start && add_marking_end && !add_apex) BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints."; - } - - const coord_t step_count = static_cast(static_cast(std::abs(ex - sx)) / approximate_step_size + 0.5); - - discretized.emplace_back(s); - for (coord_t step = 1; step < step_count; step++) - { - assert(double(sxex) * double(step) <= double(std::numeric_limits::max())); + + const coord_t step_count = lround(static_cast(std::abs(ex - sx)) / approximate_step_size); + discretized.emplace_back(start); + for (coord_t step = 1; step < step_count; ++step) { const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px); - assert(double(x) * double(x) <= double(std::numeric_limits::max())); - assert(double(x) * double(x) / double(2 * d) + double(d / 2) <= double(std::numeric_limits::max())); const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2); - - if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir)) - { + + if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir)) { discretized.emplace_back(marking_start); add_marking_start = false; } - if (add_apex && int64_t(x) * int64_t(dir) > 0) - { + + if (add_apex && int64_t(x) * int64_t(dir) > 0) { discretized.emplace_back(apex); - add_apex = false; // only add the apex just before the + add_apex = false; // only add the apex just before the } - if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir)) - { + + if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir)) { discretized.emplace_back(marking_end); add_marking_end = false; } - assert(x <= std::numeric_limits::max() && x >= std::numeric_limits::lowest()); - assert(y <= std::numeric_limits::max() && y >= std::numeric_limits::lowest()); - const Point result = rot.unapply(Point(x, y)) + pxx; + + assert(Geometry::VoronoiUtils::is_in_range(x) && Geometry::VoronoiUtils::is_in_range(y)); + const Point result = Point(x, y).rotated(rot_cos_theta, rot_sin_theta) + pxx; discretized.emplace_back(result); } + if (add_apex) - { discretized.emplace_back(apex); - } + if (add_marking_end) - { discretized.emplace_back(marking_end); - } - discretized.emplace_back(e); + + discretized.emplace_back(end); return discretized; } diff --git a/src/libslic3r/Arachne/utils/VoronoiUtils.hpp b/src/libslic3r/Arachne/utils/VoronoiUtils.hpp index ea6a8495a1..6b6abeb041 100644 --- a/src/libslic3r/Arachne/utils/VoronoiUtils.hpp +++ b/src/libslic3r/Arachne/utils/VoronoiUtils.hpp @@ -34,7 +34,7 @@ public: * Discretize a parabola based on (approximate) step size. * The \p approximate_step_size is measured parallel to the \p source_segment, not along the parabola. */ - static Points discretizeParabola(const Point &source_point, const Segment &source_segment, Point start, Point end, coord_t approximate_step_size, float transitioning_angle); + static Points discretizeParabola(const Point &source_point, const Segment &source_segment, const Point& start, const Point &end, coord_t approximate_step_size, float transitioning_angle); static inline bool is_finite(const VoronoiUtils::vd_t::vertex_type &vertex) { diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index ce24457cd1..2cb2232864 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -214,6 +214,8 @@ set(SLIC3R_SOURCES Geometry/Voronoi.hpp Geometry/VoronoiOffset.cpp Geometry/VoronoiOffset.hpp + Geometry/VoronoiUtils.hpp + Geometry/VoronoiUtils.cpp Geometry/VoronoiVisualUtils.hpp Int128.hpp JumpPointSearch.cpp @@ -464,7 +466,6 @@ set(SLIC3R_SOURCES BranchingTree/BranchingTree.hpp BranchingTree/PointCloud.cpp BranchingTree/PointCloud.hpp - Arachne/BeadingStrategy/BeadingStrategy.hpp Arachne/BeadingStrategy/BeadingStrategy.cpp Arachne/BeadingStrategy/BeadingStrategyFactory.hpp diff --git a/src/libslic3r/Geometry/VoronoiUtils.cpp b/src/libslic3r/Geometry/VoronoiUtils.cpp new file mode 100644 index 0000000000..2b4846c627 --- /dev/null +++ b/src/libslic3r/Geometry/VoronoiUtils.cpp @@ -0,0 +1,10 @@ +#include "VoronoiUtils.hpp" + +namespace Slic3r::Geometry { + +bool VoronoiUtils::is_finite(const VD::vertex_type &vertex) +{ + return std::isfinite(vertex.x()) && std::isfinite(vertex.y()); +} + +} // namespace Slic3r::Geometry \ No newline at end of file diff --git a/src/libslic3r/Geometry/VoronoiUtils.hpp b/src/libslic3r/Geometry/VoronoiUtils.hpp new file mode 100644 index 0000000000..c0f821d2db --- /dev/null +++ b/src/libslic3r/Geometry/VoronoiUtils.hpp @@ -0,0 +1,36 @@ +#ifndef slic3r_VoronoiUtils_hpp_ +#define slic3r_VoronoiUtils_hpp_ + +#include "libslic3r/Geometry/Voronoi.hpp" + +using VD = Slic3r::Geometry::VoronoiDiagram; + +namespace Slic3r::Geometry { + +class VoronoiUtils +{ +public: + static bool is_finite(const VD::vertex_type &vertex); + + template static bool is_in_range(double value) + { + return double(std::numeric_limits::lowest()) <= value && value <= double(std::numeric_limits::max()); + } + + template static bool is_in_range(const VD::vertex_type &vertex) + { + return VoronoiUtils::is_finite(vertex) && is_in_range(vertex.x()) && is_in_range(vertex.y()); + } + + template static bool is_in_range(const VD::edge_type &edge) + { + if (edge.vertex0() == nullptr || edge.vertex1() == nullptr) + return false; + + return is_in_range(*edge.vertex0()) && is_in_range(*edge.vertex1()); + } +}; + +} // namespace Slic3r::Geometry + +#endif // slic3r_VoronoiUtils_hpp_ From fb84f3113af146b8fe64bea2b5ab695682d81671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 31 Jan 2024 17:40:57 +0100 Subject: [PATCH 03/11] Introduce wrapper class around boost::polygon::voronoi_diagram. --- .../Arachne/SkeletalTrapezoidation.cpp | 54 +++++------ .../Arachne/SkeletalTrapezoidation.hpp | 23 +++-- .../Arachne/utils/PolygonsPointIndex.hpp | 2 - src/libslic3r/Geometry/MedialAxis.cpp | 2 +- src/libslic3r/Geometry/Voronoi.hpp | 89 ++++++++++++++++--- src/libslic3r/Geometry/VoronoiUtilsCgal.cpp | 8 +- src/libslic3r/MultiMaterialSegmentation.cpp | 6 +- tests/libslic3r/test_voronoi.cpp | 38 ++++---- 8 files changed, 142 insertions(+), 80 deletions(-) diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 2be3ef1949..644c7f2269 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -89,7 +89,7 @@ static void export_graph_to_svg(const std::string } #endif -SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_type& vd_node, Point p) +SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(VD::vertex_type& vd_node, Point p) { auto he_node_it = vd_node_to_he_node.find(&vd_node); if (he_node_it == vd_node_to_he_node.end()) @@ -105,7 +105,7 @@ SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_ty } } -void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector& segments) +void SkeletalTrapezoidation::transferEdge(Point from, Point to, VD::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, 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()) @@ -216,14 +216,14 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& } } -Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const std::vector& segments) +Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector& segments) { /*Terminology in this function assumes that the edge moves horizontally from left to right. This is not necessarily the case; the edge can go in any direction, but it helps to picture it in a certain direction in your head.*/ - const vd_t::cell_type* left_cell = vd_edge.cell(); - const vd_t::cell_type* right_cell = vd_edge.twin()->cell(); + const VD::cell_type* left_cell = vd_edge.cell(); + const VD::cell_type* right_cell = vd_edge.twin()->cell(); assert(VoronoiUtils::p(vd_edge.vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex0()).x() >= std::numeric_limits::lowest()); assert(VoronoiUtils::p(vd_edge.vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex0()).y() >= std::numeric_limits::lowest()); @@ -331,7 +331,7 @@ Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const } } -bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments) +bool SkeletalTrapezoidation::computePointCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, 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. @@ -340,7 +340,7 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& // 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_t::vertex_type &vert = *cell.incident_edge()->vertex0(); + 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 @@ -358,7 +358,7 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& 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 - vd_t::edge_type* vd_edge = cell.incident_edge(); + VD::edge_type* vd_edge = cell.incident_edge(); do { assert(vd_edge->is_finite()); if (Vec2i64 p1 = VoronoiUtils::p(vd_edge->vertex1()); p1 == source_point.cast()) { @@ -376,7 +376,7 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& return true; } -void SkeletalTrapezoidation::computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments) +void SkeletalTrapezoidation::computeSegmentCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, VD::edge_type*& ending_vd_edge, const std::vector& segments) { const Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); const Point from = source_segment.from(); @@ -387,7 +387,7 @@ void SkeletalTrapezoidation::computeSegmentCellRange(vd_t::cell_type& cell, Poin bool seen_possible_start = false; bool after_start = false; bool ending_edge_is_set_before_start = false; - vd_t::edge_type* edge = cell.incident_edge(); + VD::edge_type* edge = cell.incident_edge(); do { if (edge->is_infinite()) continue; @@ -431,7 +431,7 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead constructFromPolygons(polys); } -static bool has_finite_edge_with_non_finite_vertex(const Geometry::VoronoiDiagram &voronoi_diagram) +static bool has_finite_edge_with_non_finite_vertex(const VD &voronoi_diagram) { for (const VoronoiUtils::vd_t::edge_type &edge : voronoi_diagram.edges()) { if (edge.is_finite()) { @@ -444,11 +444,11 @@ static bool has_finite_edge_with_non_finite_vertex(const Geometry::VoronoiDiagra return false; } -static bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector &segments) { +static bool detect_missing_voronoi_vertex(const VD &voronoi_diagram, const std::vector &segments) { if (has_finite_edge_with_non_finite_vertex(voronoi_diagram)) return true; - for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) { + for (VD::cell_type cell : voronoi_diagram.cells()) { if (!cell.incident_edge()) continue; // There is no spoon @@ -517,9 +517,9 @@ inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalT } } -bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector &segments) +bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector &segments) { - for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) { + for (VD::cell_type cell : voronoi_diagram.cells()) { if (!cell.incident_edge()) continue; // Degenerated cell, there is no spoon @@ -530,14 +530,14 @@ bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagr const Vec2d source_segment_from = source_segment.from().cast(); const Vec2d source_segment_vec = source_segment.to().cast() - source_segment_from; - Point start_source_point, end_source_point; - VoronoiUtils::vd_t::edge_type *begin_voronoi_edge = nullptr, *end_voronoi_edge = nullptr; + Point start_source_point, end_source_point; + VD::edge_type *begin_voronoi_edge = nullptr, *end_voronoi_edge = nullptr; SkeletalTrapezoidation::computeSegmentCellRange(cell, start_source_point, end_source_point, begin_voronoi_edge, end_voronoi_edge, segments); // All Voronoi vertices must be on left side of the source segment, otherwise Voronoi diagram is invalid. // FIXME Lukas H.: Be aware that begin_voronoi_edge and end_voronoi_edge could be nullptr in some specific cases. // It mostly happens when there is some missing Voronoi, for example, in GH issue #8846 (IssuesWithMysteriousPerimeters.3mf). if (begin_voronoi_edge != nullptr && end_voronoi_edge != nullptr) - for (VoronoiUtils::vd_t::edge_type *edge = begin_voronoi_edge; edge != end_voronoi_edge; edge = edge->next()) + for (VD::edge_type *edge = begin_voronoi_edge; edge != end_voronoi_edge; edge = edge->next()) if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) return true; } @@ -555,7 +555,7 @@ enum class VoronoiDiagramStatus { // Try to detect cases when some Voronoi vertex is missing, when the Voronoi diagram // is not planar or some Voronoi edge is intersecting input segment. -VoronoiDiagramStatus detect_voronoi_diagram_known_issues(const Geometry::VoronoiDiagram &voronoi_diagram, +VoronoiDiagramStatus detect_voronoi_diagram_known_issues(const VD &voronoi_diagram, const std::vector &segments) { if (const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments); has_missing_voronoi_vertex) { @@ -571,7 +571,7 @@ VoronoiDiagramStatus detect_voronoi_diagram_known_issues(const Geometry::Voronoi } inline static std::pair try_to_fix_degenerated_voronoi_diagram_by_rotation( - Geometry::VoronoiDiagram &voronoi_diagram, + VD &voronoi_diagram, const Polygons &polys, Polygons &polys_rotated, std::vector &segments, @@ -602,7 +602,7 @@ inline static std::pair try_to_fix_degenerated_voronoi_diagram segments.emplace_back(&polys_rotated, poly_idx, point_idx); voronoi_diagram.clear(); - construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram); + voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend()); #ifdef ARACHNE_DEBUG_VORONOI { @@ -651,8 +651,8 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) } #endif - Geometry::VoronoiDiagram voronoi_diagram; - construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram); + VD voronoi_diagram; + voronoi_diagram.construct_voronoi(segments.begin(), segments.end()); #ifdef ARACHNE_DEBUG_VORONOI { @@ -692,14 +692,14 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) process_voronoi_diagram: assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty()); - for (vd_t::cell_type cell : voronoi_diagram.cells()) { + for (VD::cell_type cell : voronoi_diagram.cells()) { if (!cell.incident_edge()) continue; // There is no spoon Point start_source_point; Point end_source_point; - vd_t::edge_type* starting_voronoi_edge = nullptr; - vd_t::edge_type* ending_voronoi_edge = nullptr; + VD::edge_type* starting_voronoi_edge = nullptr; + VD::edge_type* ending_voronoi_edge = nullptr; // Compute and store result in above variables if (cell.contains_point()) { @@ -726,7 +726,7 @@ process_voronoi_diagram: constexpr bool is_next_to_start_or_end = true; graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end); - for (vd_t::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) { + for (VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) { assert(vd_edge->is_finite()); assert(VoronoiUtils::p(vd_edge->vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex0()).x() >= std::numeric_limits::lowest()); diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index e2a013b154..dfb40ceb11 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -26,8 +26,9 @@ //#define ARACHNE_DEBUG //#define ARACHNE_DEBUG_VORONOI -namespace Slic3r::Arachne -{ +namespace Slic3r::Arachne { + +using VD = Slic3r::Geometry::VoronoiDiagram; /*! * Main class of the dynamic beading strategies. @@ -50,8 +51,6 @@ deposition modeling" by Kuipers et al. */ class SkeletalTrapezoidation { - using pos_t = double; - using vd_t = boost::polygon::voronoi_diagram; using graph_t = SkeletalTrapezoidationGraph; using edge_t = STHalfEdge; using node_t = STHalfEdgeNode; @@ -168,9 +167,9 @@ protected: * mapping each voronoi VD edge to the corresponding halfedge HE edge * In case the result segment is discretized, we map the VD edge to the *last* HE edge */ - ankerl::unordered_dense::map vd_edge_to_he_edge; - ankerl::unordered_dense::map vd_node_to_he_node; - node_t& makeNode(vd_t::vertex_type& vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet. + ankerl::unordered_dense::map vd_edge_to_he_edge; + ankerl::unordered_dense::map vd_node_to_he_node; + node_t& makeNode(VD::vertex_type& vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet. /*! * (Eventual) returned 'polylines per index' result (from generateToolpaths): @@ -181,7 +180,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, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector& segments); + void transferEdge(Point from, Point to, VD::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector& segments); /*! * Discretize a Voronoi edge that represents the medial axis of a vertex- @@ -208,7 +207,7 @@ protected: * \return A number of coordinates along the edge where the edge is broken * up into discrete pieces. */ - Points discretize(const vd_t::edge_type& segment, const std::vector& segments); + Points discretize(const VD::edge_type& segment, const std::vector& segments); /*! * Compute the range of line segments that surround a cell of the skeletal @@ -234,7 +233,7 @@ protected: * /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(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments); + static bool computePointCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, VD::edge_type*& ending_vd_edge, const std::vector& segments); /*! * Compute the range of line segments that surround a cell of the skeletal @@ -260,7 +259,7 @@ protected: * /return Whether the cell is inside of the polygon. If it's outside of the * polygon we should skip processing it altogether. */ - static void computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments); + static void computeSegmentCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, 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 @@ -603,7 +602,7 @@ protected: */ void generateLocalMaximaSingleBeads(); - friend bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector &segments); + friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector &segments); }; } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp b/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp index 125b3ef926..04f017f86a 100644 --- a/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp +++ b/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp @@ -156,8 +156,6 @@ struct PathsPointIndexLocator } }; -using PolygonsPointIndexLocator = PathsPointIndexLocator; - }//namespace Slic3r::Arachne namespace std diff --git a/src/libslic3r/Geometry/MedialAxis.cpp b/src/libslic3r/Geometry/MedialAxis.cpp index aaaa970f32..3854dcee48 100644 --- a/src/libslic3r/Geometry/MedialAxis.cpp +++ b/src/libslic3r/Geometry/MedialAxis.cpp @@ -467,7 +467,7 @@ void MedialAxis::build(ThickPolylines* polylines) test(l.b.y()); } #endif // NDEBUG - construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd); + m_vd.construct_voronoi(m_lines.begin(), m_lines.end()); Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines); // static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees // std::vector skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); diff --git a/src/libslic3r/Geometry/Voronoi.hpp b/src/libslic3r/Geometry/Voronoi.hpp index 88b487c761..bfd6d17643 100644 --- a/src/libslic3r/Geometry/Voronoi.hpp +++ b/src/libslic3r/Geometry/Voronoi.hpp @@ -8,10 +8,8 @@ #include "../Line.hpp" #include "../Polyline.hpp" -#define BOOST_VORONOI_USE_GMP 1 - #ifdef _MSC_VER -// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned +// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned #pragma warning(push) #pragma warning(disable : 4146) #endif // _MSC_VER @@ -20,18 +18,85 @@ #pragma warning(pop) #endif // _MSC_VER -namespace Slic3r { +namespace Slic3r::Geometry { -namespace Geometry { - -class VoronoiDiagram : public boost::polygon::voronoi_diagram { +class VoronoiDiagram +{ public: - typedef double coord_type; - typedef boost::polygon::point_data point_type; - typedef boost::polygon::segment_data segment_type; - typedef boost::polygon::rectangle_data rect_type; + using coord_type = double; + using voronoi_diagram_type = boost::polygon::voronoi_diagram; + using point_type = boost::polygon::point_data; + using segment_type = boost::polygon::segment_data; + using rect_type = boost::polygon::rectangle_data; + + using coordinate_type = voronoi_diagram_type::coordinate_type; + using vertex_type = voronoi_diagram_type::vertex_type; + using edge_type = voronoi_diagram_type::edge_type; + using cell_type = voronoi_diagram_type::cell_type; + + using const_vertex_iterator = voronoi_diagram_type::const_vertex_iterator; + using const_edge_iterator = voronoi_diagram_type::const_edge_iterator; + using const_cell_iterator = voronoi_diagram_type::const_cell_iterator; + + using vertex_container_type = voronoi_diagram_type::vertex_container_type; + using edge_container_type = voronoi_diagram_type::edge_container_type; + using cell_container_type = voronoi_diagram_type::cell_container_type; + + VoronoiDiagram() = default; + + virtual ~VoronoiDiagram() = default; + + void clear() { m_voronoi_diagram.clear(); } + + const cell_container_type &cells() const { return m_voronoi_diagram.cells(); } + + const vertex_container_type &vertices() const { return m_voronoi_diagram.vertices(); } + + const edge_container_type &edges() const { return m_voronoi_diagram.edges(); } + + std::size_t num_cells() const { return m_voronoi_diagram.num_cells(); } + + std::size_t num_edges() const { return m_voronoi_diagram.num_edges(); } + + std::size_t num_vertices() const { return m_voronoi_diagram.num_vertices(); } + + template + typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + void>::type + construct_voronoi(const SegmentIterator first, const SegmentIterator last) + { + boost::polygon::construct_voronoi(first, last, &m_voronoi_diagram); + } + + template + typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + void>::type + construct_voronoi(const PointIterator first, const PointIterator last) + { + boost::polygon::construct_voronoi(first, last, &m_voronoi_diagram); + } + + template + typename boost::polygon::enable_if< + typename boost::polygon::gtl_and< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename boost::polygon::gtl_if::value_type>::type>::type>::type>::type, + void>::type + construct_voronoi(const PointIterator p_first, const PointIterator p_last, const SegmentIterator s_first, const SegmentIterator s_last) + { + boost::polygon::construct_voronoi(p_first, p_last, s_first, s_last, &m_voronoi_diagram); + } + +private: + voronoi_diagram_type m_voronoi_diagram; }; -} } // namespace Slicer::Geometry +} // namespace Slic3r::Geometry #endif // slic3r_Geometry_Voronoi_hpp_ diff --git a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp index 965cece814..ce97acf02a 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp @@ -254,7 +254,7 @@ static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::e } } -bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector &segments) +bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram, const std::vector &segments) { for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) { std::vector edges; @@ -271,9 +271,9 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor // Checking for CCW make sense for three and more edges. if (edges.size() > 2) { for (auto edge_it = edges.begin() ; edge_it != edges.end(); ++edge_it) { - const Geometry::VoronoiDiagram::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it); - const Geometry::VoronoiDiagram::edge_type *curr_edge = *edge_it; - const Geometry::VoronoiDiagram::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it); + const VD::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it); + const VD::edge_type *curr_edge = *edge_it; + const VD::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it); if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segments)) return false; diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 334a33c389..23f593c0eb 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -702,7 +702,7 @@ struct MMU_Graph // All Voronoi vertices are post-processes to merge very close vertices to single. Witch eliminates issues with intersection edges. // Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them. - void append_voronoi_vertices(const Geometry::VoronoiDiagram &vd, const Polygons &color_poly_tmp, BoundingBox bbox) { + void append_voronoi_vertices(const Voronoi::VD &vd, const Polygons &color_poly_tmp, BoundingBox bbox) { bbox.offset(SCALED_EPSILON); struct CPoint @@ -884,7 +884,7 @@ static inline Line clip_finite_voronoi_edge(const Voronoi::VD::edge_type &edge, static MMU_Graph build_graph(size_t layer_idx, const std::vector> &color_poly) { - Geometry::VoronoiDiagram vd; + Voronoi::VD vd; std::vector lines_colored = to_lines(color_poly); const Polygons color_poly_tmp = colored_points_to_polygon(color_poly); const Points points = to_points(color_poly_tmp); @@ -908,7 +908,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); @@ -1966,7 +1966,7 @@ TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex1-out.svg").c_str(), vd, Points(), lines); #endif @@ -2006,7 +2006,7 @@ TEST_CASE("Voronoi missing vertex 2", "[VoronoiMissingVertex2]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex2-out.svg").c_str(), vd, Points(), lines); #endif @@ -2047,7 +2047,7 @@ TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex3-out.svg").c_str(), vd, Points(), lines); #endif @@ -2091,8 +2091,8 @@ TEST_CASE("Voronoi missing vertex 4", "[VoronoiMissingVertex4]") Geometry::VoronoiDiagram vd_2; Lines lines_1 = to_lines(polygon_1); Lines lines_2 = to_lines(polygon_2); - construct_voronoi(lines_1.begin(), lines_1.end(), &vd_1); - construct_voronoi(lines_2.begin(), lines_2.end(), &vd_2); + vd_1.construct_voronoi(lines_1.begin(), lines_1.end()); + vd_2.construct_voronoi(lines_2.begin(), lines_2.end()); #ifdef VORONOI_DEBUG_OUT 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); @@ -2124,7 +2124,7 @@ TEST_CASE("Duplicate Voronoi vertices", "[Voronoi]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-duplicate-vertices-out.svg").c_str(), vd, Points(), lines); #endif @@ -2164,7 +2164,7 @@ TEST_CASE("Intersecting Voronoi edges", "[Voronoi]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-intersecting-edges-out.svg").c_str(), vd, Points(), lines); #endif @@ -2226,7 +2226,7 @@ TEST_CASE("Non-planar voronoi diagram", "[VoronoiNonPlanar]") VD vd; Lines lines = to_lines(poly); - construct_voronoi(lines.begin(), lines.end(), &vd); + vd.construct_voronoi(lines.begin(), lines.end()); #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-non-planar-out.svg").c_str(), vd, Points(), lines); #endif From 23b7c4185702019840769f1c86e7be431b1385ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 31 Jan 2024 17:41:22 +0100 Subject: [PATCH 04/11] Move ColoredLine struct and related boost traits into the header file of MultiMaterialSegmentation. --- src/libslic3r/MultiMaterialSegmentation.cpp | 23 ---------------- src/libslic3r/MultiMaterialSegmentation.hpp | 30 ++++++++++++++++++++- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 23f593c0eb..e40c081066 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -20,30 +20,7 @@ #include #include -namespace Slic3r { -struct ColoredLine { - Line line; - int color; - int poly_idx = -1; - int local_line_idx = -1; -}; -} - #include -namespace boost::polygon { -template <> -struct geometry_concept { typedef segment_concept type; }; - -template <> -struct segment_traits { - typedef coord_t coordinate_type; - typedef Slic3r::Point point_type; - - static inline point_type get(const Slic3r::ColoredLine& line, const direction_1d& dir) { - return dir.to_int() ? line.line.b : line.line.a; - } -}; -} //#define MMU_SEGMENTATION_DEBUG_GRAPH //#define MMU_SEGMENTATION_DEBUG_REGIONS diff --git a/src/libslic3r/MultiMaterialSegmentation.hpp b/src/libslic3r/MultiMaterialSegmentation.hpp index 6eb8815b16..2de93f9f05 100644 --- a/src/libslic3r/MultiMaterialSegmentation.hpp +++ b/src/libslic3r/MultiMaterialSegmentation.hpp @@ -10,13 +10,41 @@ namespace Slic3r { - class PrintObject; class ExPolygon; +using ExPolygons = std::vector; + +struct ColoredLine +{ + Line line; + int color; + int poly_idx = -1; + int local_line_idx = -1; +}; + +using ColoredLines = std::vector; // Returns MMU segmentation based on painting in MMU segmentation gizmo std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback); } // namespace Slic3r +namespace boost::polygon { +template<> struct geometry_concept +{ + typedef segment_concept type; +}; + +template<> struct segment_traits +{ + typedef coord_t coordinate_type; + typedef Slic3r::Point point_type; + + static inline point_type get(const Slic3r::ColoredLine &line, const direction_1d &dir) + { + return dir.to_int() ? line.line.b : line.line.a; + } +}; +} // namespace boost::polygon + #endif // slic3r_MultiMaterialSegmentation_hpp_ From ac33876796c9130997d5f0b72c28d013d1edbb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 31 Jan 2024 17:41:45 +0100 Subject: [PATCH 05/11] Generalize all functions in VoronoiUtils to not depend on Arachne data structures. Also, move VoronoiUtils from Arachne namespace to Geometry namespace. --- .../Arachne/SkeletalTrapezoidation.cpp | 165 +++++------- .../Arachne/SkeletalTrapezoidation.hpp | 30 +-- src/libslic3r/Arachne/utils/VoronoiUtils.cpp | 181 ------------- src/libslic3r/Arachne/utils/VoronoiUtils.hpp | 47 ---- src/libslic3r/CMakeLists.txt | 2 - src/libslic3r/Geometry/VoronoiUtils.cpp | 244 ++++++++++++++++++ src/libslic3r/Geometry/VoronoiUtils.hpp | 80 ++++++ src/libslic3r/Geometry/VoronoiUtilsCgal.cpp | 16 +- src/libslic3r/Geometry/VoronoiUtilsCgal.hpp | 4 +- 9 files changed, 393 insertions(+), 376 deletions(-) delete mode 100644 src/libslic3r/Arachne/utils/VoronoiUtils.cpp delete mode 100644 src/libslic3r/Arachne/utils/VoronoiUtils.hpp diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 644c7f2269..582f4b93cc 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -10,8 +10,6 @@ #include #include -#include "utils/VoronoiUtils.hpp" - #include "utils/linearAlg2D.hpp" #include "Utils.hpp" #include "SVG.hpp" @@ -19,6 +17,8 @@ #include "Geometry/VoronoiUtilsCgal.hpp" #include "../EdgeGrid.hpp" +#include "Geometry/VoronoiUtils.hpp" + #define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance). namespace Slic3r::Arachne @@ -218,21 +218,18 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, VD::edge_type& v Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector& segments) { + assert(Geometry::VoronoiUtils::is_in_range(vd_edge)); + /*Terminology in this function assumes that the edge moves horizontally from left to right. This is not necessarily the case; the edge can go in any direction, but it helps to picture it in a certain direction in your head.*/ - const VD::cell_type* left_cell = vd_edge.cell(); - const VD::cell_type* right_cell = vd_edge.twin()->cell(); + const VD::cell_type *left_cell = vd_edge.cell(); + const VD::cell_type *right_cell = vd_edge.twin()->cell(); - assert(VoronoiUtils::p(vd_edge.vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex0()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge.vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex0()).y() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge.vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex1()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge.vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge.vertex1()).y() >= std::numeric_limits::lowest()); + Point start = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast(); + Point end = Geometry::VoronoiUtils::to_point(vd_edge.vertex1()).cast(); - Point start = VoronoiUtils::p(vd_edge.vertex0()).cast(); - Point end = VoronoiUtils::p(vd_edge.vertex1()).cast(); - bool point_left = left_cell->contains_point(); bool point_right = right_cell->contains_point(); if ((!point_left && !point_right) || vd_edge.is_secondary()) // Source vert is directly connected to source segment @@ -241,20 +238,20 @@ Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const st } else if (point_left != point_right) //This is a parabolic edge between a point and a line. { - Point p = VoronoiUtils::getSourcePoint(*(point_left ? left_cell : right_cell), segments); - const Segment& s = VoronoiUtils::getSourceSegment(*(point_left ? right_cell : left_cell), segments); - return VoronoiUtils::discretizeParabola(p, s, start, end, discretization_step_size, transitioning_angle); + Point p = Geometry::VoronoiUtils::get_source_point(*(point_left ? left_cell : right_cell), segments.begin(), segments.end()); + const Segment& s = Geometry::VoronoiUtils::get_source_segment(*(point_left ? right_cell : left_cell), segments.begin(), segments.end()); + return Geometry::VoronoiUtils::discretize_parabola(p, s, start, end, discretization_step_size, transitioning_angle); } else //This is a straight edge between two points. { /*While the edge is straight, it is still discretized since the part becomes narrower between the two points. As such it may need different beadings along the way.*/ - Point left_point = VoronoiUtils::getSourcePoint(*left_cell, segments); - Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments); - coord_t d = (right_point - left_point).cast().norm(); - Point middle = (left_point + right_point) / 2; - Point x_axis_dir = perp(Point(right_point - left_point)); + Point left_point = Geometry::VoronoiUtils::get_source_point(*left_cell, segments.begin(), segments.end()); + Point right_point = Geometry::VoronoiUtils::get_source_point(*right_cell, segments.begin(), segments.end()); + coord_t d = (right_point - left_point).cast().norm(); + Point middle = (left_point + right_point) / 2; + Point x_axis_dir = perp(Point(right_point - left_point)); coord_t x_axis_length = x_axis_dir.cast().norm(); const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge. @@ -345,11 +342,11 @@ bool SkeletalTrapezoidation::computePointCellRange(VD::cell_type& cell, Point& s 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 = VoronoiUtils::getSourcePoint(cell, segments); - const PolygonsPointIndex source_point_index = VoronoiUtils::getSourcePointIndex(cell, segments); - Vec2i64 some_point = VoronoiUtils::p(cell.incident_edge()->vertex0()); + 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 = VoronoiUtils::p(cell.incident_edge()->vertex1()); + 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. @@ -361,13 +358,13 @@ bool SkeletalTrapezoidation::computePointCellRange(VD::cell_type& cell, Point& s VD::edge_type* vd_edge = cell.incident_edge(); do { assert(vd_edge->is_finite()); - if (Vec2i64 p1 = VoronoiUtils::p(vd_edge->vertex1()); p1 == source_point.cast()) { + 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((VoronoiUtils::p(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."); + 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()); @@ -376,47 +373,6 @@ bool SkeletalTrapezoidation::computePointCellRange(VD::cell_type& cell, Point& s return true; } -void SkeletalTrapezoidation::computeSegmentCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, VD::edge_type*& ending_vd_edge, const std::vector& segments) -{ - const Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); - const Point from = source_segment.from(); - const Point to = source_segment.to(); - - // Find starting edge - // Find end edge - bool seen_possible_start = false; - bool after_start = false; - bool ending_edge_is_set_before_start = false; - VD::edge_type* edge = cell.incident_edge(); - do { - if (edge->is_infinite()) - continue; - - Vec2i64 v0 = VoronoiUtils::p(edge->vertex0()); - Vec2i64 v1 = VoronoiUtils::p(edge->vertex1()); - - assert(!(v0 == to.cast() && v1 == from.cast() )); - if (v0 == to.cast() && !after_start) { // Use the last edge which starts in source_segment.to - starting_vd_edge = edge; - seen_possible_start = true; - } - else if (seen_possible_start) { - after_start = true; - } - - if (v1 == from.cast() && (!ending_vd_edge || ending_edge_is_set_before_start)) { - ending_edge_is_set_before_start = !after_start; - ending_vd_edge = edge; - } - } while (edge = edge->next(), edge != cell.incident_edge()); - - assert(starting_vd_edge && ending_vd_edge); - assert(starting_vd_edge != ending_vd_edge); - - start_source_point = source_segment.to(); - end_source_point = source_segment.from(); -} - 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, @@ -433,11 +389,11 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead static bool has_finite_edge_with_non_finite_vertex(const VD &voronoi_diagram) { - for (const VoronoiUtils::vd_t::edge_type &edge : voronoi_diagram.edges()) { + for (const VD::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())) + if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !Geometry::VoronoiUtils::is_finite(*edge.vertex0()) || + !Geometry::VoronoiUtils::is_finite(*edge.vertex1())) return true; } } @@ -453,7 +409,7 @@ static bool detect_missing_voronoi_vertex(const VD &voronoi_diagram, const std:: continue; // There is no spoon if (cell.contains_segment()) { - const SkeletalTrapezoidation::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); + const SkeletalTrapezoidation::Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segments.begin(), segments.end()); const Point from = source_segment.from(); const Point to = source_segment.to(); @@ -462,15 +418,15 @@ static bool detect_missing_voronoi_vertex(const VD &voronoi_diagram, const std:: bool seen_possible_start = false; bool after_start = false; bool ending_edge_is_set_before_start = false; - VoronoiUtils::vd_t::edge_type *starting_vd_edge = nullptr; - VoronoiUtils::vd_t::edge_type *ending_vd_edge = nullptr; - VoronoiUtils::vd_t::edge_type *edge = cell.incident_edge(); + VD::edge_type *starting_vd_edge = nullptr; + VD::edge_type *ending_vd_edge = nullptr; + VD::edge_type *edge = cell.incident_edge(); do { - if (edge->is_infinite() || edge->vertex0() == nullptr || edge->vertex1() == nullptr || !VoronoiUtils::is_finite(*edge->vertex0()) || !VoronoiUtils::is_finite(*edge->vertex1())) + if (edge->is_infinite() || edge->vertex0() == nullptr || edge->vertex1() == nullptr || !Geometry::VoronoiUtils::is_finite(*edge->vertex0()) || !Geometry::VoronoiUtils::is_finite(*edge->vertex1())) continue; - Vec2i64 v0 = VoronoiUtils::p(edge->vertex0()); - Vec2i64 v1 = VoronoiUtils::p(edge->vertex1()); + Vec2i64 v0 = Geometry::VoronoiUtils::to_point(edge->vertex0()); + Vec2i64 v1 = Geometry::VoronoiUtils::to_point(edge->vertex1()); assert(!(v0 == to.cast() && v1 == from.cast())); if (v0 == to.cast() && !after_start) { // Use the last edge which starts in source_segment.to @@ -517,7 +473,7 @@ inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalT } } -bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector &segments) +bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector &segments) { for (VD::cell_type cell : voronoi_diagram.cells()) { if (!cell.incident_edge()) @@ -526,18 +482,16 @@ bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, c if (!cell.contains_segment()) continue; // Skip cells that don't contain segments. - const VoronoiUtils::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); - const Vec2d source_segment_from = source_segment.from().cast(); - const Vec2d source_segment_vec = source_segment.to().cast() - source_segment_from; + const PolygonsSegmentIndex &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segments.begin(), segments.end()); + const Vec2d source_segment_from = source_segment.from().cast(); + const Vec2d source_segment_vec = source_segment.to().cast() - source_segment_from; - Point start_source_point, end_source_point; - VD::edge_type *begin_voronoi_edge = nullptr, *end_voronoi_edge = nullptr; - SkeletalTrapezoidation::computeSegmentCellRange(cell, start_source_point, end_source_point, begin_voronoi_edge, end_voronoi_edge, segments); + Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.begin(), segments.end()); // All Voronoi vertices must be on left side of the source segment, otherwise Voronoi diagram is invalid. // FIXME Lukas H.: Be aware that begin_voronoi_edge and end_voronoi_edge could be nullptr in some specific cases. // It mostly happens when there is some missing Voronoi, for example, in GH issue #8846 (IssuesWithMysteriousPerimeters.3mf). - if (begin_voronoi_edge != nullptr && end_voronoi_edge != nullptr) - for (VD::edge_type *edge = begin_voronoi_edge; edge != end_voronoi_edge; edge = edge->next()) + if (cell_range.edge_begin != nullptr && cell_range.edge_end != nullptr) + for (VD::edge_type *edge = cell_range.edge_begin; edge != cell_range.edge_end; edge = edge->next()) if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) return true; } @@ -652,7 +606,7 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) #endif VD voronoi_diagram; - voronoi_diagram.construct_voronoi(segments.begin(), segments.end()); + voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend()); #ifdef ARACHNE_DEBUG_VORONOI { @@ -696,10 +650,10 @@ process_voronoi_diagram: if (!cell.incident_edge()) continue; // There is no spoon - Point start_source_point; - Point end_source_point; - VD::edge_type* starting_voronoi_edge = nullptr; - VD::edge_type* ending_voronoi_edge = nullptr; + Point start_source_point; + Point end_source_point; + VD::edge_type *starting_voronoi_edge = nullptr; + VD::edge_type *ending_voronoi_edge = nullptr; // Compute and store result in above variables if (cell.contains_point()) { @@ -708,7 +662,12 @@ process_voronoi_diagram: continue; } else { assert(cell.contains_segment()); - computeSegmentCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments); + 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; + starting_voronoi_edge = cell_range.edge_begin; + ending_voronoi_edge = cell_range.edge_end; } if (!starting_voronoi_edge || !ending_voronoi_edge) { @@ -717,33 +676,25 @@ process_voronoi_diagram: } // Copy start to end edge to graph - edge_t* prev_edge = nullptr; - assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() >= std::numeric_limits::lowest()); - transferEdge(start_source_point, VoronoiUtils::p(starting_voronoi_edge->vertex1()).cast(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); - node_t* starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()]; + assert(Geometry::VoronoiUtils::is_in_range(*starting_voronoi_edge)); + edge_t *prev_edge = nullptr; + transferEdge(start_source_point, Geometry::VoronoiUtils::to_point(starting_voronoi_edge->vertex1()).cast(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); + node_t *starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()]; starting_node->data.distance_to_boundary = 0; constexpr bool is_next_to_start_or_end = true; graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end); for (VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) { assert(vd_edge->is_finite()); + assert(Geometry::VoronoiUtils::is_in_range(*vd_edge)); - assert(VoronoiUtils::p(vd_edge->vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex0()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge->vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex0()).y() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge->vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex1()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(vd_edge->vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex1()).y() >= std::numeric_limits::lowest()); - - Point v1 = VoronoiUtils::p(vd_edge->vertex0()).cast(); - Point v2 = VoronoiUtils::p(vd_edge->vertex1()).cast(); + Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast(); + Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast(); transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments); - graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge); } - assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() >= std::numeric_limits::lowest()); - transferEdge(VoronoiUtils::p(ending_voronoi_edge->vertex0()).cast(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); + transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); prev_edge->to->data.distance_to_boundary = 0; } diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index dfb40ceb11..15ef1cb8f2 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -11,8 +11,6 @@ #include -#include - #include "utils/HalfEdgeGraph.hpp" #include "utils/PolygonsSegmentIndex.hpp" #include "utils/ExtrusionJunction.hpp" @@ -235,32 +233,6 @@ protected: */ static bool computePointCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, VD::edge_type*& ending_vd_edge, const std::vector& segments); - /*! - * Compute the range of line segments that surround a cell of the skeletal - * graph that belongs to a line segment of the medial axis. - * - * This should only be used on cells that belong to a central line segment - * of the skeletal graph, e.g. trapezoid cells, not triangular 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 void computeSegmentCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, 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 @@ -602,7 +574,7 @@ protected: */ void generateLocalMaximaSingleBeads(); - friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector &segments); + friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector &segments); }; } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp deleted file mode 100644 index f06c15da64..0000000000 --- a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp +++ /dev/null @@ -1,181 +0,0 @@ -//Copyright (c) 2021 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. - -#include -#include -#include - -#include "linearAlg2D.hpp" -#include "VoronoiUtils.hpp" -#include "libslic3r/Geometry/VoronoiUtils.hpp" - -namespace Slic3r::Arachne -{ - -Vec2i64 VoronoiUtils::p(const vd_t::vertex_type *node) -{ - const double x = node->x(); - const double y = node->y(); - assert(std::isfinite(x) && std::isfinite(y)); - assert(x <= double(std::numeric_limits::max()) && x >= std::numeric_limits::lowest()); - assert(y <= double(std::numeric_limits::max()) && y >= std::numeric_limits::lowest()); - return {int64_t(x + 0.5 - (x < 0)), int64_t(y + 0.5 - (y < 0))}; // Round to the nearest integer coordinates. -} - -Point VoronoiUtils::getSourcePoint(const vd_t::cell_type& cell, const std::vector& segments) -{ - assert(cell.contains_point()); - if(!cell.contains_point()) - BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!"; - - switch (cell.source_category()) { - case boost::polygon::SOURCE_CATEGORY_SINGLE_POINT: - assert(false && "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!\n"); - BOOST_LOG_TRIVIAL(error) << "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!"; - break; - case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: - assert(cell.source_index() < segments.size()); - return boost::polygon::segment_traits::get(segments[cell.source_index()], boost::polygon::LOW); - break; - case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: - assert(cell.source_index() < segments.size()); - return boost::polygon::segment_traits::get(segments[cell.source_index()], boost::polygon::HIGH); - break; - default: - assert(false && "getSourcePoint should only be called on point cells!\n"); - break; - } - - assert(false && "cell.source_category() is equal to an invalid value!\n"); - BOOST_LOG_TRIVIAL(error) << "cell.source_category() is equal to an invalid value!"; - return {}; -} - -PolygonsPointIndex VoronoiUtils::getSourcePointIndex(const vd_t::cell_type& cell, const std::vector& segments) -{ - assert(cell.contains_point()); - if(!cell.contains_point()) - BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!"; - - assert(cell.source_category() != boost::polygon::SOURCE_CATEGORY_SINGLE_POINT); - switch (cell.source_category()) { - case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: { - assert(cell.source_index() < segments.size()); - return segments[cell.source_index()]; - break; - } - case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: { - assert(cell.source_index() < segments.size()); - return segments[cell.source_index()].next(); - break; - } - default: - assert(false && "getSourcePoint should only be called on point cells!\n"); - break; - } - PolygonsPointIndex ret = segments[cell.source_index()]; - return ++ret; -} - -const VoronoiUtils::Segment &VoronoiUtils::getSourceSegment(const vd_t::cell_type &cell, const std::vector &segments) -{ - assert(cell.contains_segment()); - if (!cell.contains_segment()) - BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source segment!"; - - return segments[cell.source_index()]; -} - -Points VoronoiUtils::discretizeParabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, const coord_t approximate_step_size, float transitioning_angle) -{ - Points discretized; - // x is distance of point projected on the segment ab - // xx is point projected on the segment ab - const Point a = source_segment.from(); - const Point b = source_segment.to(); - const Point ab = b - a; - const Point as = start - a; - const Point ae = end - a; - const coord_t ab_size = ab.cast().norm(); - const coord_t sx = as.cast().dot(ab.cast()) / ab_size; - const coord_t ex = ae.cast().dot(ab.cast()) / ab_size; - const coord_t sxex = ex - sx; - - const Point ap = source_point - a; - const coord_t px = ap.cast().dot(ab.cast()) / ab_size; - - Point pxx; - Line(a, b).distance_to_infinite_squared(source_point, &pxx); - const Point ppxx = pxx - source_point; - const coord_t d = ppxx.cast().norm(); - - const Vec2d rot = perp(ppxx).cast().normalized(); - const double rot_cos_theta = rot.x(); - const double rot_sin_theta = rot.y(); - - if (d == 0) { - discretized.emplace_back(start); - discretized.emplace_back(end); - return discretized; - } - - const double marking_bound = atan(transitioning_angle * 0.5); - int64_t msx = -marking_bound * int64_t(d); // projected marking_start - int64_t mex = marking_bound * int64_t(d); // projected marking_end - - const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2; - Point marking_start = Point(coord_t(msx), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx; - Point marking_end = Point(coord_t(mex), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx; - const int dir = (sx > ex) ? -1 : 1; - if (dir < 0) { - std::swap(marking_start, marking_end); - std::swap(msx, mex); - } - - bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir); - bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir); - - const Point apex = Point(0, d / 2).rotated(rot_cos_theta, rot_sin_theta) + pxx; - bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0; - - assert(!add_marking_start || !add_marking_end || add_apex); - if (add_marking_start && add_marking_end && !add_apex) - BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints."; - - const coord_t step_count = lround(static_cast(std::abs(ex - sx)) / approximate_step_size); - discretized.emplace_back(start); - for (coord_t step = 1; step < step_count; ++step) { - const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px); - const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2); - - if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir)) { - discretized.emplace_back(marking_start); - add_marking_start = false; - } - - if (add_apex && int64_t(x) * int64_t(dir) > 0) { - discretized.emplace_back(apex); - add_apex = false; // only add the apex just before the - } - - if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir)) { - discretized.emplace_back(marking_end); - add_marking_end = false; - } - - assert(Geometry::VoronoiUtils::is_in_range(x) && Geometry::VoronoiUtils::is_in_range(y)); - const Point result = Point(x, y).rotated(rot_cos_theta, rot_sin_theta) + pxx; - discretized.emplace_back(result); - } - - if (add_apex) - discretized.emplace_back(apex); - - if (add_marking_end) - discretized.emplace_back(marking_end); - - discretized.emplace_back(end); - return discretized; -} - -}//namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/VoronoiUtils.hpp b/src/libslic3r/Arachne/utils/VoronoiUtils.hpp deleted file mode 100644 index 6b6abeb041..0000000000 --- a/src/libslic3r/Arachne/utils/VoronoiUtils.hpp +++ /dev/null @@ -1,47 +0,0 @@ -//Copyright (c) 2020 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. - - -#ifndef UTILS_VORONOI_UTILS_H -#define UTILS_VORONOI_UTILS_H - -#include - - -#include - -#include "PolygonsSegmentIndex.hpp" - -namespace Slic3r::Arachne -{ - -/*! - */ -class VoronoiUtils -{ -public: - using Segment = PolygonsSegmentIndex; - using voronoi_data_t = double; - using vd_t = boost::polygon::voronoi_diagram; - - static Point getSourcePoint(const vd_t::cell_type &cell, const std::vector &segments); - static const Segment &getSourceSegment(const vd_t::cell_type &cell, const std::vector &segments); - static PolygonsPointIndex getSourcePointIndex(const vd_t::cell_type &cell, const std::vector &segments); - - static Vec2i64 p(const vd_t::vertex_type *node); - - /*! - * Discretize a parabola based on (approximate) step size. - * The \p approximate_step_size is measured parallel to the \p source_segment, not along the parabola. - */ - static Points discretizeParabola(const Point &source_point, const Segment &source_segment, const Point& start, const Point &end, coord_t approximate_step_size, float transitioning_angle); - - static inline bool is_finite(const VoronoiUtils::vd_t::vertex_type &vertex) - { - return std::isfinite(vertex.x()) && std::isfinite(vertex.y()); - } -}; - -} // namespace Slic3r::Arachne - -#endif // UTILS_VORONOI_UTILS_H diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 2cb2232864..67e51f5dd1 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -496,8 +496,6 @@ set(SLIC3R_SOURCES Arachne/utils/PolygonsSegmentIndex.hpp Arachne/utils/PolylineStitcher.hpp Arachne/utils/PolylineStitcher.cpp - Arachne/utils/VoronoiUtils.hpp - Arachne/utils/VoronoiUtils.cpp Arachne/SkeletalTrapezoidation.hpp Arachne/SkeletalTrapezoidation.cpp Arachne/SkeletalTrapezoidationEdge.hpp diff --git a/src/libslic3r/Geometry/VoronoiUtils.cpp b/src/libslic3r/Geometry/VoronoiUtils.cpp index 2b4846c627..6348540406 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.cpp +++ b/src/libslic3r/Geometry/VoronoiUtils.cpp @@ -1,7 +1,251 @@ +#include +#include + #include "VoronoiUtils.hpp" namespace Slic3r::Geometry { +using PolygonsSegmentIndexConstIt = std::vector::const_iterator; +using LinesIt = Lines::iterator; +using ColoredLinesIt = ColoredLines::iterator; + +// Explicit template instantiation. +template LinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, LinesIt, LinesIt); +template ColoredLinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); +template PolygonsSegmentIndexConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); +template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, LinesIt, LinesIt); +template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); +template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); +template SegmentCellRange VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, LinesIt, LinesIt); +template SegmentCellRange VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); +template SegmentCellRange VoronoiUtils::compute_segment_cell_range(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); + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename std::iterator_traits::reference>::type +VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + if (!cell.contains_segment()) + throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source segment!"); + + if (cell.source_index() >= size_t(std::distance(segment_begin, segment_end))) + throw Slic3r::OutOfRange("Voronoi cell source index is out of range!"); + + return *(segment_begin + cell.source_index()); +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename boost::polygon::segment_point_type::value_type>::type>::type +VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + using Segment = typename std::iterator_traits::value_type; + + if (!cell.contains_point()) + throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!"); + + if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) { + assert(int(cell.source_index()) < std::distance(segment_begin, segment_end)); + const SegmentIterator segment_it = segment_begin + cell.source_index(); + return boost::polygon::segment_traits::get(*segment_it, boost::polygon::LOW); + } else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) { + assert(int(cell.source_index()) < std::distance(segment_begin, segment_end)); + const SegmentIterator segment_it = segment_begin + cell.source_index(); + return boost::polygon::segment_traits::get(*segment_it, boost::polygon::HIGH); + } else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) { + throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!"); + } else { + throw Slic3r::InvalidArgument("Function get_source_point() should only be called on point cells!"); + } +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + Arachne::PolygonsPointIndex>::type +VoronoiUtils::get_source_point_index(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + if (!cell.contains_point()) + throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!"); + + if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) { + assert(int(cell.source_index()) < std::distance(segment_begin, segment_end)); + const SegmentIterator segment_it = segment_begin + cell.source_index(); + return (*segment_it); + } else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) { + assert(int(cell.source_index()) < std::distance(segment_begin, segment_end)); + const SegmentIterator segment_it = segment_begin + cell.source_index(); + return (*segment_it).next(); + } else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) { + throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!"); + } else { + throw Slic3r::InvalidArgument("Function get_source_point_index() should only be called on point cells!"); + } +} + +template +typename boost::polygon::enable_if::type>::type>::type, + Points>::type +VoronoiUtils::discretize_parabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, const coord_t approximate_step_size, float transitioning_angle) +{ + Points discretized; + // x is distance of point projected on the segment ab + // xx is point projected on the segment ab + const Point a = source_segment.from(); + const Point b = source_segment.to(); + const Point ab = b - a; + const Point as = start - a; + const Point ae = end - a; + const coord_t ab_size = ab.cast().norm(); + const coord_t sx = as.cast().dot(ab.cast()) / ab_size; + const coord_t ex = ae.cast().dot(ab.cast()) / ab_size; + const coord_t sxex = ex - sx; + + const Point ap = source_point - a; + const coord_t px = ap.cast().dot(ab.cast()) / ab_size; + + Point pxx; + Line(a, b).distance_to_infinite_squared(source_point, &pxx); + const Point ppxx = pxx - source_point; + const coord_t d = ppxx.cast().norm(); + + const Vec2d rot = perp(ppxx).cast().normalized(); + const double rot_cos_theta = rot.x(); + const double rot_sin_theta = rot.y(); + + if (d == 0) { + discretized.emplace_back(start); + discretized.emplace_back(end); + return discretized; + } + + const double marking_bound = atan(transitioning_angle * 0.5); + int64_t msx = -marking_bound * int64_t(d); // projected marking_start + int64_t mex = marking_bound * int64_t(d); // projected marking_end + + const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2; + Point marking_start = Point(coord_t(msx), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx; + Point marking_end = Point(coord_t(mex), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx; + const int dir = (sx > ex) ? -1 : 1; + if (dir < 0) { + std::swap(marking_start, marking_end); + std::swap(msx, mex); + } + + bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir); + bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir); + + const Point apex = Point(0, d / 2).rotated(rot_cos_theta, rot_sin_theta) + pxx; + bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0; + + assert(!add_marking_start || !add_marking_end || add_apex); + if (add_marking_start && add_marking_end && !add_apex) + BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints."; + + const coord_t step_count = lround(static_cast(std::abs(ex - sx)) / approximate_step_size); + discretized.emplace_back(start); + for (coord_t step = 1; step < step_count; ++step) { + const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px); + const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2); + + if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir)) { + discretized.emplace_back(marking_start); + add_marking_start = false; + } + + if (add_apex && int64_t(x) * int64_t(dir) > 0) { + discretized.emplace_back(apex); + add_apex = false; // only add the apex just before the + } + + if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir)) { + discretized.emplace_back(marking_end); + add_marking_end = false; + } + + assert(is_in_range(x) && is_in_range(y)); + const Point result = Point(x, y).rotated(rot_cos_theta, rot_sin_theta) + pxx; + discretized.emplace_back(result); + } + + if (add_apex) + discretized.emplace_back(apex); + + if (add_marking_end) + discretized.emplace_back(marking_end); + + discretized.emplace_back(end); + return discretized; +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + Geometry::SegmentCellRange< + typename boost::polygon::segment_point_type::value_type>::type>>::type +VoronoiUtils::compute_segment_cell_range(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 SegmentCellRange = SegmentCellRange; + + const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end); + const Point from = boost::polygon::segment_traits::get(source_segment, boost::polygon::LOW); + const Point to = boost::polygon::segment_traits::get(source_segment, boost::polygon::HIGH); + const Vec2i64 from_i64 = from.template cast(); + const Vec2i64 to_i64 = to.template cast(); + + // FIXME @hejllukas: Ensure that there is no infinite edge during iteration between edge_begin and edge_end. + SegmentCellRange cell_range(to, from); + + // Find starting edge and end edge + bool seen_possible_start = false; + bool after_start = false; + bool ending_edge_is_set_before_start = false; + VD::edge_type *edge = cell.incident_edge(); + do { + if (edge->is_infinite()) + continue; + + Vec2i64 v0 = Geometry::VoronoiUtils::to_point(edge->vertex0()); + Vec2i64 v1 = Geometry::VoronoiUtils::to_point(edge->vertex1()); + assert(v0 != to_i64 || v1 != from_i64); + + if (v0 == to_i64 && !after_start) { // Use the last edge which starts in source_segment.to + cell_range.edge_begin = edge; + seen_possible_start = true; + } else if (seen_possible_start) { + after_start = true; + } + + if (v1 == from_i64 && (!cell_range.edge_end || ending_edge_is_set_before_start)) { + ending_edge_is_set_before_start = !after_start; + cell_range.edge_end = edge; + } + } while (edge = edge->next(), edge != cell.incident_edge()); + + return cell_range; +} + +Vec2i64 VoronoiUtils::to_point(const VD::vertex_type *vertex) +{ + const double x = vertex->x(), y = vertex->y(); + + assert(std::isfinite(x) && std::isfinite(y)); + assert(is_in_range(x) && is_in_range(y)); + + return {std::llround(x), std::llround(y)}; +} + bool VoronoiUtils::is_finite(const VD::vertex_type &vertex) { return std::isfinite(vertex.x()) && std::isfinite(vertex.y()); diff --git a/src/libslic3r/Geometry/VoronoiUtils.hpp b/src/libslic3r/Geometry/VoronoiUtils.hpp index c0f821d2db..d21390772f 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.hpp +++ b/src/libslic3r/Geometry/VoronoiUtils.hpp @@ -2,16 +2,96 @@ #define slic3r_VoronoiUtils_hpp_ #include "libslic3r/Geometry/Voronoi.hpp" +#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp" using VD = Slic3r::Geometry::VoronoiDiagram; 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. + VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts. + 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) + {} + + bool is_valid() const { return edge_begin && edge_end && edge_begin != edge_end; } +}; + class VoronoiUtils { public: + static Vec2i64 to_point(const VD::vertex_type *vertex); + static bool is_finite(const VD::vertex_type &vertex); + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename std::iterator_traits::reference>::type + get_source_segment(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename boost::polygon::segment_point_type::value_type>::type>::type + get_source_point(const VoronoiDiagram::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + Arachne::PolygonsPointIndex>::type + get_source_point_index(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + + /** + * Discretize a parabola based on (approximate) step size. + * + * Adapted from CuraEngine VoronoiUtils::discretizeParabola by Tim Kuipers @BagelOrb and @Ghostkeeper. + * + * @param approximate_step_size is measured parallel to the source_segment, not along the parabola. + */ + template + static typename boost::polygon::enable_if::type>::type>::type, + Points>::type + discretize_parabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, coord_t approximate_step_size, float transitioning_angle); + + /** + * Compute the range of line segments that surround a cell of the skeletal + * graph that belongs to a line segment of the medial axis. + * + * This should only be used on cells that belong to a central line segment + * of the skeletal graph, e.g. trapezoid cells, not triangular 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::SegmentCellRange< + typename boost::polygon::segment_point_type::value_type>::type>>::type + compute_segment_cell_range(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/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp index ce97acf02a..9ac0169388 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp @@ -7,7 +7,7 @@ #include #include "libslic3r/Geometry/Voronoi.hpp" -#include "libslic3r/Arachne/utils/VoronoiUtils.hpp" +#include "libslic3r/Geometry/VoronoiUtils.hpp" #include "VoronoiUtilsCgal.hpp" @@ -178,22 +178,22 @@ struct ParabolicSegment const CGAL::Orientation is_focus_on_left; }; -inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector &segments) +inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector &segments) { assert(edge.is_curved()); const VD::cell_type *left_cell = edge.cell(); const VD::cell_type *right_cell = edge.twin()->cell(); - const Point focus_pt = VoronoiUtils::getSourcePoint(*(left_cell->contains_point() ? left_cell : right_cell), segments); - const VoronoiUtils::Segment &directrix = VoronoiUtils::getSourceSegment(*(left_cell->contains_point() ? right_cell : left_cell), segments); - CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt))); + const Point focus_pt = VoronoiUtils::get_source_point(*(left_cell->contains_point() ? left_cell : right_cell), segments.begin(), segments.end()); + const PolygonsSegmentIndex &directrix = VoronoiUtils::get_source_segment(*(left_cell->contains_point() ? right_cell : left_cell), segments.begin(), segments.end()); + CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt))); assert(focus_side == CGAL::Orientation::LEFT_TURN || focus_side == CGAL::Orientation::RIGHT_TURN); return {focus_pt, Line(directrix.from(), directrix.to()), make_linef(edge), focus_side}; } -inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const std::vector &segments) { +inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const std::vector &segments) { assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0())); CGAL::Orientation orientation; if (edge_a.is_linear() && edge_b.is_linear()) { @@ -230,7 +230,7 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed return orientation; } -static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::edge_type &second, const VD::edge_type &third, const std::vector &segments) +static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::edge_type &second, const VD::edge_type &third, const std::vector &segments) { assert(is_equal(*first.vertex0(), *second.vertex0()) && is_equal(*second.vertex0(), *third.vertex0())); @@ -254,7 +254,7 @@ static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::e } } -bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram, const std::vector &segments) +bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram, const std::vector &segments) { for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) { std::vector edges; diff --git a/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp index 1d47c33a3a..eef32df679 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp @@ -6,7 +6,7 @@ #define slic3r_VoronoiUtilsCgal_hpp_ #include "Voronoi.hpp" -#include "../Arachne/utils/VoronoiUtils.hpp" +#include "../Arachne/utils/PolygonsSegmentIndex.hpp" namespace Slic3r::Geometry { class VoronoiDiagram; @@ -18,7 +18,7 @@ public: static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram); // Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex. - static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector &segments); + static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector &segments); }; } // namespace Slic3r::Geometry From 78108e647ca3e2912924f0ebbd7252aa7db6585e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 31 Jan 2024 17:42:06 +0100 Subject: [PATCH 06/11] Generalize VoronoiUtilsCgal to not depend on Arachne data structures. --- .../Arachne/SkeletalTrapezoidation.cpp | 2 +- src/libslic3r/Geometry/VoronoiUtilsCgal.cpp | 104 ++++++++++++------ src/libslic3r/Geometry/VoronoiUtilsCgal.hpp | 8 +- 3 files changed, 79 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 582f4b93cc..42fb7535d5 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -517,7 +517,7 @@ VoronoiDiagramStatus detect_voronoi_diagram_known_issues(const VD } else if (const bool has_voronoi_edge_intersecting_input_segment = detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments); has_voronoi_edge_intersecting_input_segment) { // Detection if Voronoi edge is intersecting input segment detects at least one model in GH issue #8446. return VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT; - } else if (const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments); !is_voronoi_diagram_planar) { + } else if (const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments.begin(), segments.end()); !is_voronoi_diagram_planar) { // Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446. return VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM; } diff --git a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp index 9ac0169388..4af62e0569 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp @@ -8,14 +8,24 @@ #include "libslic3r/Geometry/Voronoi.hpp" #include "libslic3r/Geometry/VoronoiUtils.hpp" +#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" #include "VoronoiUtilsCgal.hpp" using VD = Slic3r::Geometry::VoronoiDiagram; -using namespace Slic3r::Arachne; namespace Slic3r::Geometry { +using PolygonsSegmentIndexConstIt = std::vector::const_iterator; +using LinesIt = Lines::iterator; +using ColoredLinesIt = ColoredLines::iterator; + +// Explicit template instantiation. +template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, LinesIt, LinesIt); +template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, ColoredLinesIt, ColoredLinesIt); +template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); + // The tangent vector of the parabola is computed based on the Proof of the reflective property. // https://en.wikipedia.org/wiki/Parabola#Proof_of_the_reflective_property // https://math.stackexchange.com/q/2439647/2439663#comment5039739_2439663 @@ -121,30 +131,30 @@ using ParabolicTangentToSegmentOrientation = impl::ParabolicTangentToSegmentOrie using ParabolicTangentToParabolicTangentOrientation = impl::ParabolicTangentToParabolicTangentOrientationPredicateFiltered; using CGAL_Point = impl::K::Point_2; -inline static CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; } -inline static CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; } -inline static CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; } +inline CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; } +inline CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; } +inline CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; } -inline static Linef make_linef(const VD::edge_type &edge) +inline Linef make_linef(const VD::edge_type &edge) { const VD::vertex_type *v0 = edge.vertex0(); const VD::vertex_type *v1 = edge.vertex1(); return {Vec2d(v0->x(), v0->y()), Vec2d(v1->x(), v1->y())}; } -[[maybe_unused]] inline static bool is_equal(const VD::vertex_type &first, const VD::vertex_type &second) { return first.x() == second.x() && first.y() == second.y(); } +[[maybe_unused]] inline bool is_equal(const VD::vertex_type &vertex_first, const VD::vertex_type &vertex_second) { return vertex_first.x() == vertex_second.x() && vertex_first.y() == vertex_second.y(); } // FIXME Lukas H.: Also includes parabolic segments. bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram) { - using CGAL_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2; - using CGAL_Segment = CGAL::Arr_segment_traits_2::Curve_2; - auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_Point { return {pt.x(), pt.y()}; }; + using CGAL_E_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2; + using CGAL_E_Segment = CGAL::Arr_segment_traits_2::Curve_2; + auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_E_Point { return {pt.x(), pt.y()}; }; assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(), [](const VD::edge_type &edge) { return edge.color() == 0; })); - std::vector segments; + std::vector segments; segments.reserve(voronoi_diagram.num_edges()); for (const VD::edge_type &edge : voronoi_diagram.edges()) { @@ -163,7 +173,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_ for (const VD::edge_type &edge : voronoi_diagram.edges()) edge.color(0); - std::vector intersections_pt; + std::vector intersections_pt; CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt)); return intersections_pt.empty(); } @@ -178,29 +188,44 @@ struct ParabolicSegment const CGAL::Orientation is_focus_on_left; }; -inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector &segments) +template +inline static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + ParabolicSegment>::type +get_parabolic_segment(const VD::edge_type &edge, const SegmentIterator segment_begin, const SegmentIterator segment_end) { + using Segment = typename std::iterator_traits::value_type; assert(edge.is_curved()); const VD::cell_type *left_cell = edge.cell(); const VD::cell_type *right_cell = edge.twin()->cell(); - const Point focus_pt = VoronoiUtils::get_source_point(*(left_cell->contains_point() ? left_cell : right_cell), segments.begin(), segments.end()); - const PolygonsSegmentIndex &directrix = VoronoiUtils::get_source_segment(*(left_cell->contains_point() ? right_cell : left_cell), segments.begin(), segments.end()); - CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt))); + const Point focus_pt = VoronoiUtils::get_source_point(*(left_cell->contains_point() ? left_cell : right_cell), segment_begin, segment_end); + const Segment &directrix = VoronoiUtils::get_source_segment(*(left_cell->contains_point() ? right_cell : left_cell), segment_begin, segment_end); + CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt))); assert(focus_side == CGAL::Orientation::LEFT_TURN || focus_side == CGAL::Orientation::RIGHT_TURN); - return {focus_pt, Line(directrix.from(), directrix.to()), make_linef(edge), focus_side}; + + const Point directrix_from = boost::polygon::segment_traits::get(directrix, boost::polygon::LOW); + const Point directrix_to = boost::polygon::segment_traits::get(directrix, boost::polygon::HIGH); + return {focus_pt, Line(directrix_from, directrix_to), make_linef(edge), focus_side}; } -inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const std::vector &segments) { +template +inline static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + CGAL::Orientation>::type +orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0())); CGAL::Orientation orientation; if (edge_a.is_linear() && edge_b.is_linear()) { orientation = CGAL::orientation(to_cgal_point(edge_a.vertex0()), to_cgal_point(edge_a.vertex1()), to_cgal_point(edge_b.vertex1())); } else if (edge_a.is_curved() && edge_b.is_curved()) { - const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segments); - const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segments); + const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segment_begin, segment_end); + const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segment_begin, segment_end); orientation = ParabolicTangentToParabolicTangentOrientation{}(to_cgal_point(parabolic_a.segment.a), to_cgal_point(parabolic_a.focus), to_cgal_point(parabolic_a.directrix.a), @@ -216,7 +241,7 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed const VD::edge_type &linear_edge = edge_a.is_curved() ? edge_b : edge_a; const VD::edge_type ¶bolic_edge = edge_a.is_curved() ? edge_a : edge_b; - const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segments); + const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segment_begin, segment_end); orientation = ParabolicTangentToSegmentOrientation{}(to_cgal_point(parabolic.segment.a), to_cgal_point(linear_edge.vertex1()), to_cgal_point(parabolic.focus), to_cgal_point(parabolic.directrix.a), @@ -230,39 +255,54 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed return orientation; } -static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::edge_type &second, const VD::edge_type &third, const std::vector &segments) +template +static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + bool>::type +check_if_three_edges_are_ccw(const VD::edge_type &edge_first, + const VD::edge_type &edge_second, + const VD::edge_type &edge_third, + const SegmentIterator segment_begin, + const SegmentIterator segment_end) { - assert(is_equal(*first.vertex0(), *second.vertex0()) && is_equal(*second.vertex0(), *third.vertex0())); + assert(is_equal(*edge_first.vertex0(), *edge_second.vertex0()) && is_equal(*edge_second.vertex0(), *edge_third.vertex0())); - CGAL::Orientation orientation = orientation_of_two_edges(first, second, segments); + CGAL::Orientation orientation = orientation_of_two_edges(edge_first, edge_second, segment_begin, segment_end); if (orientation == CGAL::Orientation::COLLINEAR) { // The first two edges are collinear, so the third edge must be on the right side on the first of them. - return orientation_of_two_edges(first, third, segments) == CGAL::Orientation::RIGHT_TURN; + return orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end) == CGAL::Orientation::RIGHT_TURN; } else if (orientation == CGAL::Orientation::LEFT_TURN) { // CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is bellow PI. // So we need to check if test_pt isn't between them. - CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments); - CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments); + CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end); + CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end); return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN); } else { assert(orientation == CGAL::Orientation::RIGHT_TURN); // CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is upper PI. // So we need to check if test_pt is between them. - CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments); - CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments); + CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end); + CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end); return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN); } } -bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram, const std::vector &segments) +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + bool>::type +VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram, + const SegmentIterator segment_begin, + const SegmentIterator segment_end) { for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) { std::vector edges; const VD::edge_type *edge = vertex.incident_edge(); do { - if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && - VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1())) + if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1())) edges.emplace_back(edge); edge = edge->rot_next(); @@ -275,7 +315,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram const VD::edge_type *curr_edge = *edge_it; const VD::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it); - if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segments)) + if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segment_begin, segment_end)) return false; } } diff --git a/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp index eef32df679..924b6d76d7 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp @@ -18,8 +18,12 @@ public: static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram); // Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex. - static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector &segments); - + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + bool>::type + is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end); }; } // namespace Slic3r::Geometry From 227e82a6ba9162dd10be48b2ac13e9671577e0be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 31 Jan 2024 17:42:38 +0100 Subject: [PATCH 07/11] Generalize all detection on invalid Voronoi diagrams to not depend on Arachne data structures. Also, all detections are moved from SkeletalTrapezoidation.cpp to Voronoi class. --- .../Arachne/SkeletalTrapezoidation.cpp | 177 ++--------------- src/libslic3r/CMakeLists.txt | 3 + src/libslic3r/Geometry/Voronoi.cpp | 184 ++++++++++++++++++ src/libslic3r/Geometry/Voronoi.hpp | 90 +++++++-- 4 files changed, 277 insertions(+), 177 deletions(-) create mode 100644 src/libslic3r/Geometry/Voronoi.cpp diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 42fb7535d5..ad1e9dc0b7 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -387,77 +387,6 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead constructFromPolygons(polys); } -static bool has_finite_edge_with_non_finite_vertex(const VD &voronoi_diagram) -{ - for (const VD::edge_type &edge : voronoi_diagram.edges()) { - if (edge.is_finite()) { - assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr); - if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !Geometry::VoronoiUtils::is_finite(*edge.vertex0()) || - !Geometry::VoronoiUtils::is_finite(*edge.vertex1())) - return true; - } - } - return false; -} - -static bool detect_missing_voronoi_vertex(const VD &voronoi_diagram, const std::vector &segments) { - if (has_finite_edge_with_non_finite_vertex(voronoi_diagram)) - return true; - - for (VD::cell_type cell : voronoi_diagram.cells()) { - if (!cell.incident_edge()) - continue; // There is no spoon - - if (cell.contains_segment()) { - const SkeletalTrapezoidation::Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segments.begin(), segments.end()); - const Point from = source_segment.from(); - const Point to = source_segment.to(); - - // Find starting edge - // Find end edge - bool seen_possible_start = false; - bool after_start = false; - bool ending_edge_is_set_before_start = false; - VD::edge_type *starting_vd_edge = nullptr; - VD::edge_type *ending_vd_edge = nullptr; - VD::edge_type *edge = cell.incident_edge(); - do { - if (edge->is_infinite() || edge->vertex0() == nullptr || edge->vertex1() == nullptr || !Geometry::VoronoiUtils::is_finite(*edge->vertex0()) || !Geometry::VoronoiUtils::is_finite(*edge->vertex1())) - continue; - - Vec2i64 v0 = Geometry::VoronoiUtils::to_point(edge->vertex0()); - Vec2i64 v1 = Geometry::VoronoiUtils::to_point(edge->vertex1()); - - assert(!(v0 == to.cast() && v1 == from.cast())); - if (v0 == to.cast() && !after_start) { // Use the last edge which starts in source_segment.to - starting_vd_edge = edge; - seen_possible_start = true; - } else if (seen_possible_start) { - after_start = true; - } - - if (v1 == from.cast() && (!ending_vd_edge || ending_edge_is_set_before_start)) { - ending_edge_is_set_before_start = !after_start; - ending_vd_edge = edge; - } - } while (edge = edge->next(), edge != cell.incident_edge()); - - if (!starting_vd_edge || !ending_vd_edge || starting_vd_edge == ending_vd_edge) - return true; - } - } - - return false; -} - -static bool has_missing_twin_edge(const SkeletalTrapezoidationGraph &graph) -{ - for (const auto &edge : graph.edges) - if (edge.twin == nullptr) - return true; - return false; -} - using PointMap = SkeletalTrapezoidation::PointMap; inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph, @@ -473,57 +402,6 @@ inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalT } } -bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector &segments) -{ - for (VD::cell_type cell : voronoi_diagram.cells()) { - if (!cell.incident_edge()) - continue; // Degenerated cell, there is no spoon - - if (!cell.contains_segment()) - continue; // Skip cells that don't contain segments. - - const PolygonsSegmentIndex &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segments.begin(), segments.end()); - const Vec2d source_segment_from = source_segment.from().cast(); - const Vec2d source_segment_vec = source_segment.to().cast() - source_segment_from; - - Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.begin(), segments.end()); - // All Voronoi vertices must be on left side of the source segment, otherwise Voronoi diagram is invalid. - // FIXME Lukas H.: Be aware that begin_voronoi_edge and end_voronoi_edge could be nullptr in some specific cases. - // It mostly happens when there is some missing Voronoi, for example, in GH issue #8846 (IssuesWithMysteriousPerimeters.3mf). - if (cell_range.edge_begin != nullptr && cell_range.edge_end != nullptr) - for (VD::edge_type *edge = cell_range.edge_begin; edge != cell_range.edge_end; edge = edge->next()) - if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) - return true; - } - - return false; -} - -enum class VoronoiDiagramStatus { - NO_ISSUE_DETECTED, - MISSING_VORONOI_VERTEX, - NON_PLANAR_VORONOI_DIAGRAM, - VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT, - OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION -}; - -// Try to detect cases when some Voronoi vertex is missing, when the Voronoi diagram -// is not planar or some Voronoi edge is intersecting input segment. -VoronoiDiagramStatus detect_voronoi_diagram_known_issues(const VD &voronoi_diagram, - const std::vector &segments) -{ - if (const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments); has_missing_voronoi_vertex) { - return VoronoiDiagramStatus::MISSING_VORONOI_VERTEX; - } else if (const bool has_voronoi_edge_intersecting_input_segment = detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments); has_voronoi_edge_intersecting_input_segment) { - // Detection if Voronoi edge is intersecting input segment detects at least one model in GH issue #8446. - return VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT; - } else if (const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments.begin(), segments.end()); !is_voronoi_diagram_planar) { - // Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446. - return VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM; - } - return VoronoiDiagramStatus::NO_ISSUE_DETECTED; -} - inline static std::pair try_to_fix_degenerated_voronoi_diagram_by_rotation( VD &voronoi_diagram, const Polygons &polys, @@ -565,7 +443,7 @@ inline static std::pair try_to_fix_degenerated_voronoi_diagram } #endif - if (detect_voronoi_diagram_known_issues(voronoi_diagram, segments) == VoronoiDiagramStatus::NO_ISSUE_DETECTED) + if (VD::detect_known_issues(voronoi_diagram, segments.cbegin(), segments.cend()) == VD::IssueType::NO_ISSUE_DETECTED) break; } @@ -617,34 +495,33 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) // When any Voronoi vertex is missing, the Voronoi diagram is not planar, or some voronoi edge is // intersecting input segment, rotate the input polygon and try again. - VoronoiDiagramStatus status = detect_voronoi_diagram_known_issues(voronoi_diagram, segments); + VD::IssueType status = VD::detect_known_issues(voronoi_diagram, segments.cbegin(), segments.cend()); const std::vector fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11}; double fixed_by_angle = fix_angles.front(); PointMap vertex_mapping; // polys_copy is referenced through items stored in the std::vector segments. Polygons polys_copy = polys; - if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) { - if (status == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX) + if (status != VD::IssueType::NO_ISSUE_DETECTED) { + if (status == VD::IssueType::MISSING_VORONOI_VERTEX) BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth."; - else if (status == VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM) + else if (status == VD::IssueType::NON_PLANAR_VORONOI_DIAGRAM) BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth."; - else if (status == VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) + else if (status == VD::IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth."; std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles); - VoronoiDiagramStatus status_after_fix = detect_voronoi_diagram_known_issues(voronoi_diagram, segments); - assert(status_after_fix == VoronoiDiagramStatus::NO_ISSUE_DETECTED); - if (status_after_fix == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX) + VD::IssueType status_after_fix = VD::detect_known_issues(voronoi_diagram, segments.cbegin(), segments.cend()); + assert(status_after_fix == VD::IssueType::NO_ISSUE_DETECTED); + if (status_after_fix == VD::IssueType::MISSING_VORONOI_VERTEX) BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input."; - else if (status_after_fix == VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM) + else if (status_after_fix == VD::IssueType::NON_PLANAR_VORONOI_DIAGRAM) BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input."; - else if (status_after_fix == VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) + else if (status_after_fix == VD::IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input."; } -process_voronoi_diagram: assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty()); for (VD::cell_type cell : voronoi_diagram.cells()) { if (!cell.incident_edge()) @@ -698,37 +575,7 @@ process_voronoi_diagram: prev_edge->to->data.distance_to_boundary = 0; } - // For some input polygons, as in GH issues #8474 and #8514 resulting Voronoi diagram is degenerated because it is not planar. - // When this degenerated Voronoi diagram is processed, the resulting half-edge structure contains some edges that don't have - // a twin edge. Based on this, we created a fast mechanism that detects those causes and tries to recompute the Voronoi - // diagram on slightly rotated input polygons that usually make the Voronoi generator generate a non-degenerated Voronoi diagram. - if (status == VoronoiDiagramStatus::NO_ISSUE_DETECTED && has_missing_twin_edge(this->graph)) { - BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth."; - status = VoronoiDiagramStatus::OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION; - std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles); - - assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments)); - if (detect_missing_voronoi_vertex(voronoi_diagram, segments)) - BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex after the rotation of input."; - - assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram)); - - this->graph.edges.clear(); - this->graph.nodes.clear(); - this->vd_edge_to_he_edge.clear(); - this->vd_node_to_he_node.clear(); - - goto process_voronoi_diagram; - } - - if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) { - assert(!has_missing_twin_edge(this->graph)); - - if (has_missing_twin_edge(this->graph)) - BOOST_LOG_TRIVIAL(error) << "Detected degenerated Voronoi diagram even after the rotation of input."; - } - - if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) + if (status != VD::IssueType::NO_ISSUE_DETECTED) rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fixed_by_angle, vertex_mapping); #ifdef ARACHNE_DEBUG diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 67e51f5dd1..9e6d1434fb 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -496,6 +496,9 @@ set(SLIC3R_SOURCES Arachne/utils/PolygonsSegmentIndex.hpp Arachne/utils/PolylineStitcher.hpp Arachne/utils/PolylineStitcher.cpp + Geometry/Voronoi.cpp + Geometry/VoronoiUtils.hpp + Geometry/VoronoiUtils.cpp Arachne/SkeletalTrapezoidation.hpp Arachne/SkeletalTrapezoidation.cpp Arachne/SkeletalTrapezoidationEdge.hpp diff --git a/src/libslic3r/Geometry/Voronoi.cpp b/src/libslic3r/Geometry/Voronoi.cpp new file mode 100644 index 0000000000..0aab12a3ee --- /dev/null +++ b/src/libslic3r/Geometry/Voronoi.cpp @@ -0,0 +1,184 @@ +#include "Voronoi.hpp" + +#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp" +#include "libslic3r/Geometry/VoronoiUtils.hpp" +#include "libslic3r/Geometry/VoronoiUtilsCgal.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" + +#include + +namespace Slic3r::Geometry { + +using PolygonsSegmentIndexConstIt = std::vector::const_iterator; +using LinesIt = Lines::iterator; +using ColoredLinesIt = ColoredLines::iterator; + +// Explicit template instantiation. +template void VoronoiDiagram::construct_voronoi(LinesIt, LinesIt, bool); +template void VoronoiDiagram::construct_voronoi(ColoredLinesIt, ColoredLinesIt, bool); +template void VoronoiDiagram::construct_voronoi(PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt, bool); + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + void>::type +VoronoiDiagram::construct_voronoi(const SegmentIterator segment_begin, const SegmentIterator segment_end, const bool try_to_repair_if_needed) { + boost::polygon::construct_voronoi(segment_begin, segment_end, &m_voronoi_diagram); + if (try_to_repair_if_needed) { + if (m_issue_type = detect_known_issues(*this, segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) { + if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) { + BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth."; + } else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) { + BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth."; + } else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) { + 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 { + BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue, input polygons will be rotated back and forth."; + } + + if (m_issue_type = try_to_repair_degenerated_voronoi_diagram(segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) { + if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) { + BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input."; + } else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) { + BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input."; + } else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) { + 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 { + BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue even after the rotation of input."; + } + + m_state = State::REPAIR_UNSUCCESSFUL; + } else { + m_state = State::REPAIR_SUCCESSFUL; + } + } else { + m_state = State::REPAIR_NOT_NEEDED; + m_issue_type = IssueType::NO_ISSUE_DETECTED; + } + } else { + m_state = State::UNKNOWN; + m_issue_type = IssueType::UNKNOWN; + } +} + +void VoronoiDiagram::clear() +{ + if (m_is_modified) { + m_vertices.clear(); + m_edges.clear(); + m_cells.clear(); + m_is_modified = false; + } else { + m_voronoi_diagram.clear(); + } + + m_state = State::UNKNOWN; + m_issue_type = IssueType::UNKNOWN; +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + 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; + } 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)) { + // Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446. + return IssueType::NON_PLANAR_VORONOI_DIAGRAM; + } + + return IssueType::NO_ISSUE_DETECTED; +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + VoronoiDiagram::IssueType>::type +VoronoiDiagram::detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram, + 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 SegmentCellRange = SegmentCellRange; + + for (VD::cell_type cell : voronoi_diagram.cells()) { + if (cell.is_degenerate() || !cell.contains_segment()) + continue; // Skip degenerated cell that has no spoon. Also, skip a cell that doesn't contain a segment. + + if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segment_begin, segment_end); cell_range.is_valid()) { + // Detection if Voronoi edge is intersecting input segment. + // It detects this type of issue at least in GH issues #8446, #8474 and #8514. + + const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end); + const Vec2d source_segment_from = boost::polygon::segment_traits::get(source_segment, boost::polygon::LOW).template cast(); + const Vec2d source_segment_to = boost::polygon::segment_traits::get(source_segment, boost::polygon::HIGH).template cast(); + const Vec2d source_segment_vec = source_segment_to - source_segment_from; + + // All Voronoi vertices must be on the left side of the source segment, otherwise the Voronoi diagram is invalid. + for (const VD::edge_type *edge = cell_range.edge_begin; edge != cell_range.edge_end; edge = edge->next()) { + if (edge->is_infinite()) { + // When there is a missing Voronoi vertex, we may encounter an infinite Voronoi edge. + // This happens, for example, in GH issue #8846. + return IssueType::MISSING_VORONOI_VERTEX; + } else if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) { + return IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT; + } + } + } else { + // When there is a missing Voronoi vertex (especially at one of the endpoints of the input segment), + // the returned cell_range is marked as invalid. + // It detects this type of issue at least in GH issue #8846. + return IssueType::MISSING_VORONOI_VERTEX; + } + } + + return IssueType::NO_ISSUE_DETECTED; +} + +bool VoronoiDiagram::has_finite_edge_with_non_finite_vertex(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 false; +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + VoronoiDiagram::IssueType>::type +VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram(const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + return this->m_issue_type; +} + +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + VoronoiDiagram::IssueType>::type +VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram_by_rotation(const SegmentIterator segment_begin, + const SegmentIterator segment_end, + const double fix_angle) +{ + return this->m_issue_type; +} + +} // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry/Voronoi.hpp b/src/libslic3r/Geometry/Voronoi.hpp index bfd6d17643..95b42a263c 100644 --- a/src/libslic3r/Geometry/Voronoi.hpp +++ b/src/libslic3r/Geometry/Voronoi.hpp @@ -42,33 +42,52 @@ public: using edge_container_type = voronoi_diagram_type::edge_container_type; using cell_container_type = voronoi_diagram_type::cell_container_type; + enum class IssueType { + NO_ISSUE_DETECTED, + FINITE_EDGE_WITH_NON_FINITE_VERTEX, + MISSING_VORONOI_VERTEX, + NON_PLANAR_VORONOI_DIAGRAM, + VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT, + UNKNOWN // Repairs are disabled in the constructor. + }; + + enum class State { + REPAIR_NOT_NEEDED, // The original Voronoi diagram doesn't have any issue. + REPAIR_SUCCESSFUL, // The original Voronoi diagram has some issues, but it was repaired. + REPAIR_UNSUCCESSFUL, // The original Voronoi diagram has some issues, but it wasn't repaired. + UNKNOWN // Repairs are disabled in the constructor. + }; + VoronoiDiagram() = default; virtual ~VoronoiDiagram() = default; - void clear() { m_voronoi_diagram.clear(); } + IssueType get_issue_type() const { return m_issue_type; } - const cell_container_type &cells() const { return m_voronoi_diagram.cells(); } + State get_state() const { return m_state; } - const vertex_container_type &vertices() const { return m_voronoi_diagram.vertices(); } + bool is_valid() const { return m_state != State::REPAIR_UNSUCCESSFUL; } - const edge_container_type &edges() const { return m_voronoi_diagram.edges(); } + void clear(); - std::size_t num_cells() const { return m_voronoi_diagram.num_cells(); } + const vertex_container_type &vertices() const { return m_is_modified ? m_vertices : m_voronoi_diagram.vertices(); } - std::size_t num_edges() const { return m_voronoi_diagram.num_edges(); } + const edge_container_type &edges() const { return m_is_modified ? m_edges : m_voronoi_diagram.edges(); } - std::size_t num_vertices() const { return m_voronoi_diagram.num_vertices(); } + const cell_container_type &cells() const { return m_is_modified ? m_cells : m_voronoi_diagram.cells(); } + + std::size_t num_vertices() const { return m_is_modified ? m_vertices.size() : m_voronoi_diagram.num_vertices(); } + + std::size_t num_edges() const { return m_is_modified ? m_edges.size() : m_voronoi_diagram.num_edges(); } + + std::size_t num_cells() const { return m_is_modified ? m_cells.size() : m_voronoi_diagram.num_cells(); } template typename boost::polygon::enable_if< typename boost::polygon::gtl_if::value_type>::type>::type>::type, void>::type - construct_voronoi(const SegmentIterator first, const SegmentIterator last) - { - boost::polygon::construct_voronoi(first, last, &m_voronoi_diagram); - } + construct_voronoi(SegmentIterator segment_begin, SegmentIterator segment_end, bool try_to_repair_if_needed = true); template typename boost::polygon::enable_if< @@ -78,6 +97,8 @@ public: construct_voronoi(const PointIterator first, const PointIterator last) { boost::polygon::construct_voronoi(first, last, &m_voronoi_diagram); + m_state = State::UNKNOWN; + m_issue_type = IssueType::UNKNOWN; } template @@ -91,10 +112,55 @@ public: construct_voronoi(const PointIterator p_first, const PointIterator p_last, const SegmentIterator s_first, const SegmentIterator s_last) { boost::polygon::construct_voronoi(p_first, p_last, s_first, s_last, &m_voronoi_diagram); + m_state = State::UNKNOWN; + m_issue_type = IssueType::UNKNOWN; } + // Try to detect cases when some Voronoi vertex is missing, when the Voronoi diagram + // is not planar or some Voronoi edge is intersecting input segment. + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + IssueType>::type + detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end); + + template + typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + VoronoiDiagram::IssueType>::type + try_to_repair_degenerated_voronoi_diagram_by_rotation(SegmentIterator segment_begin, SegmentIterator segment_end, double fix_angle); + + template + typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + VoronoiDiagram::IssueType>::type + try_to_repair_degenerated_voronoi_diagram(SegmentIterator segment_begin, SegmentIterator segment_end); + private: - voronoi_diagram_type m_voronoi_diagram; + + // Detect issues related to Voronoi cells, or that can be detected by iterating over Voronoi cells. + // The first type of issue that can be detected is a missing Voronoi vertex, especially when it is + // missing at one of the endpoints of the input segment. + // The second type of issue that can be detected is a Voronoi edge that intersects the input segment. + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + 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); + + voronoi_diagram_type m_voronoi_diagram; + vertex_container_type m_vertices; + edge_container_type m_edges; + cell_container_type m_cells; + bool m_is_modified = false; + State m_state = State::UNKNOWN; + IssueType m_issue_type = IssueType::UNKNOWN; }; } // namespace Slic3r::Geometry From a358f13de2ce67c47620b14ad119f2d1aea45ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 31 Jan 2024 17:43:01 +0100 Subject: [PATCH 08/11] Generalize fixing of invalid Voronoi diagram by rotating back and forth. Now, this fixing operation can be applied to any Voronoi diagram constructed from segments. --- .../Arachne/SkeletalTrapezoidation.cpp | 97 ---------- .../Arachne/SkeletalTrapezoidation.hpp | 1 - src/libslic3r/Geometry/Voronoi.cpp | 174 +++++++++++++++++- src/libslic3r/Geometry/Voronoi.hpp | 31 ++++ src/libslic3r/Geometry/VoronoiUtils.cpp | 18 ++ src/libslic3r/Geometry/VoronoiUtils.hpp | 2 + src/libslic3r/Geometry/VoronoiUtilsCgal.cpp | 1 + 7 files changed, 224 insertions(+), 100 deletions(-) diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index ad1e9dc0b7..53814c605d 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -387,71 +387,6 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead constructFromPolygons(polys); } -using PointMap = SkeletalTrapezoidation::PointMap; - -inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph, - const double fix_angle, - const PointMap &vertex_mapping) -{ - for (STHalfEdgeNode &node : graph.nodes) { - // If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction. - if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end()) - node.p = node_it->second; - else - node.p.rotate(-fix_angle); - } -} - -inline static std::pair try_to_fix_degenerated_voronoi_diagram_by_rotation( - VD &voronoi_diagram, - const Polygons &polys, - Polygons &polys_rotated, - std::vector &segments, - const std::vector &fix_angles) -{ - const Polygons polys_rotated_original = polys_rotated; - double fixed_by_angle = fix_angles.front(); - PointMap vertex_mapping; - - for (const double &fix_angle : fix_angles) { - vertex_mapping.clear(); - polys_rotated = polys_rotated_original; - fixed_by_angle = fix_angle; - - for (Polygon &poly : polys_rotated) - poly.rotate(fix_angle); - - assert(polys_rotated.size() == polys.size()); - for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) { - assert(polys_rotated[poly_idx].size() == polys[poly_idx].size()); - for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx) - vertex_mapping.insert({polys_rotated[poly_idx][point_idx], polys[poly_idx][point_idx]}); - } - - segments.clear(); - for (size_t poly_idx = 0; poly_idx < polys_rotated.size(); poly_idx++) - for (size_t point_idx = 0; point_idx < polys_rotated[poly_idx].size(); point_idx++) - segments.emplace_back(&polys_rotated, poly_idx, point_idx); - - voronoi_diagram.clear(); - voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend()); - -#ifdef ARACHNE_DEBUG_VORONOI - { - static int iRun = 0; - dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-rotated-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys)); - } -#endif - - if (VD::detect_known_issues(voronoi_diagram, segments.cbegin(), segments.cend()) == VD::IssueType::NO_ISSUE_DETECTED) - break; - } - - assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram)); - - return {vertex_mapping, fixed_by_angle}; -} - void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) { #ifdef ARACHNE_DEBUG @@ -493,35 +428,6 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) } #endif - // When any Voronoi vertex is missing, the Voronoi diagram is not planar, or some voronoi edge is - // intersecting input segment, rotate the input polygon and try again. - VD::IssueType status = VD::detect_known_issues(voronoi_diagram, segments.cbegin(), segments.cend()); - const std::vector fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11}; - double fixed_by_angle = fix_angles.front(); - - PointMap vertex_mapping; - // polys_copy is referenced through items stored in the std::vector segments. - Polygons polys_copy = polys; - if (status != VD::IssueType::NO_ISSUE_DETECTED) { - if (status == VD::IssueType::MISSING_VORONOI_VERTEX) - BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth."; - else if (status == VD::IssueType::NON_PLANAR_VORONOI_DIAGRAM) - BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth."; - else if (status == VD::IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) - BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth."; - - std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles); - - VD::IssueType status_after_fix = VD::detect_known_issues(voronoi_diagram, segments.cbegin(), segments.cend()); - assert(status_after_fix == VD::IssueType::NO_ISSUE_DETECTED); - if (status_after_fix == VD::IssueType::MISSING_VORONOI_VERTEX) - BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input."; - else if (status_after_fix == VD::IssueType::NON_PLANAR_VORONOI_DIAGRAM) - BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input."; - else if (status_after_fix == VD::IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) - BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input."; - } - assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty()); for (VD::cell_type cell : voronoi_diagram.cells()) { if (!cell.incident_edge()) @@ -575,9 +481,6 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) prev_edge->to->data.distance_to_boundary = 0; } - if (status != VD::IssueType::NO_ISSUE_DETECTED) - rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fixed_by_angle, vertex_mapping); - #ifdef ARACHNE_DEBUG assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram)); #endif diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index 15ef1cb8f2..9d377e6145 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -80,7 +80,6 @@ class SkeletalTrapezoidation public: using Segment = PolygonsSegmentIndex; - using PointMap = ankerl::unordered_dense::map; using NodeSet = ankerl::unordered_dense::set; /*! diff --git a/src/libslic3r/Geometry/Voronoi.cpp b/src/libslic3r/Geometry/Voronoi.cpp index 0aab12a3ee..404c5c7634 100644 --- a/src/libslic3r/Geometry/Voronoi.cpp +++ b/src/libslic3r/Geometry/Voronoi.cpp @@ -81,6 +81,74 @@ void VoronoiDiagram::clear() m_issue_type = IssueType::UNKNOWN; } +void VoronoiDiagram::copy_to_local(voronoi_diagram_type &voronoi_diagram) { + m_edges.clear(); + m_cells.clear(); + m_vertices.clear(); + + // Copy Voronoi edges. + m_edges.reserve(voronoi_diagram.num_edges()); + for (const edge_type &edge : voronoi_diagram.edges()) { + m_edges.emplace_back(edge.is_linear(), edge.is_primary()); + m_edges.back().color(edge.color()); + } + + // Copy Voronoi cells. + m_cells.reserve(voronoi_diagram.num_cells()); + for (const cell_type &cell : voronoi_diagram.cells()) { + m_cells.emplace_back(cell.source_index(), cell.source_category()); + m_cells.back().color(cell.color()); + + if (cell.incident_edge()) { + size_t incident_edge_idx = cell.incident_edge() - voronoi_diagram.edges().data(); + m_cells.back().incident_edge(&m_edges[incident_edge_idx]); + } + } + + // Copy Voronoi vertices. + m_vertices.reserve(voronoi_diagram.num_vertices()); + for (const vertex_type &vertex : voronoi_diagram.vertices()) { + m_vertices.emplace_back(vertex.x(), vertex.y()); + m_vertices.back().color(vertex.color()); + + if (vertex.incident_edge()) { + size_t incident_edge_idx = vertex.incident_edge() - voronoi_diagram.edges().data(); + m_vertices.back().incident_edge(&m_edges[incident_edge_idx]); + } + } + + // Assign all pointers for each Voronoi edge. + for (const edge_type &old_edge : voronoi_diagram.edges()) { + size_t edge_idx = &old_edge - voronoi_diagram.edges().data(); + edge_type &new_edge = m_edges[edge_idx]; + + if (old_edge.cell()) { + size_t cell_idx = old_edge.cell() - voronoi_diagram.cells().data(); + new_edge.cell(&m_cells[cell_idx]); + } + + if (old_edge.vertex0()) { + size_t vertex0_idx = old_edge.vertex0() - voronoi_diagram.vertices().data(); + new_edge.vertex0(&m_vertices[vertex0_idx]); + } + + if (old_edge.twin()) { + size_t twin_edge_idx = old_edge.twin() - voronoi_diagram.edges().data(); + new_edge.twin(&m_edges[twin_edge_idx]); + } + + if (old_edge.next()) { + size_t next_edge_idx = old_edge.next() - voronoi_diagram.edges().data(); + new_edge.next(&m_edges[next_edge_idx]); + } + + if (old_edge.prev()) { + size_t prev_edge_idx = old_edge.prev() - voronoi_diagram.edges().data(); + new_edge.prev(&m_edges[prev_edge_idx]); + } + } +} + template typename boost::polygon::enable_if< typename boost::polygon::gtl_if::type VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram(const SegmentIterator segment_begin, const SegmentIterator segment_end) { - return this->m_issue_type; + IssueType issue_type = m_issue_type; + + const std::vector fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11}; + for (const double fix_angle : fix_angles) { + issue_type = try_to_repair_degenerated_voronoi_diagram_by_rotation(segment_begin, segment_end, fix_angle); + if (issue_type == IssueType::NO_ISSUE_DETECTED) { + return issue_type; + } + } + + return issue_type; +} + +inline VD::vertex_type::color_type encode_input_segment_endpoint(const VD::cell_type::source_index_type cell_source_index, const boost::polygon::direction_1d dir) +{ + return (cell_source_index + 1) << 1 | (dir.to_int() ? 1 : 0); +} + +template +inline typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + typename boost::polygon::segment_point_type::value_type>::type>::type +decode_input_segment_endpoint(const VD::vertex_type::color_type color, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + using SegmentType = typename std::iterator_traits::value_type; + using PointType = typename boost::polygon::segment_traits::point_type; + + const size_t segment_idx = (color >> 1) - 1; + const SegmentIterator segment_it = segment_begin + segment_idx; + const PointType source_point = boost::polygon::segment_traits::get(*segment_it, ((color & 1) ? boost::polygon::HIGH : + boost::polygon::LOW)); + return source_point; } template @@ -178,7 +278,77 @@ VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram_by_rotation(const Segm const SegmentIterator segment_end, const double fix_angle) { - return this->m_issue_type; + using SegmentType = typename std::iterator_traits::value_type; + using PointType = typename boost::polygon::segment_traits::point_type; + + // Copy all segments and rotate their vertices. + std::vector segments_rotated; + segments_rotated.reserve(std::distance(segment_begin, segment_end)); + for (auto segment_it = segment_begin; segment_it != segment_end; ++segment_it) { + PointType from = boost::polygon::segment_traits::get(*segment_it, boost::polygon::LOW); + PointType to = boost::polygon::segment_traits::get(*segment_it, boost::polygon::HIGH); + segments_rotated.emplace_back(from.rotated(fix_angle), to.rotated(fix_angle)); + } + + VoronoiDiagram::voronoi_diagram_type voronoi_diagram_rotated; + boost::polygon::construct_voronoi(segments_rotated.begin(), segments_rotated.end(), &voronoi_diagram_rotated); + + this->copy_to_local(voronoi_diagram_rotated); + const IssueType issue_type = detect_known_issues(*this, segments_rotated.begin(), segments_rotated.end()); + + // We want to remap all Voronoi vertices at the endpoints of input segments + // to ensure that Voronoi vertices at endpoints will be preserved after rotation. + // So we assign every Voronoi vertices color to map this Vertex into input segments. + for (cell_type cell : m_cells) { + if (cell.is_degenerate()) + continue; + + if (cell.contains_segment()) { + if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segments_rotated.begin(), segments_rotated.end()); cell_range.is_valid()) { + if (cell_range.edge_end->vertex1()->color() == 0) { + // Vertex 1 of edge_end points to the starting endpoint of the input segment (from() or line.a). + VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::LOW); + cell_range.edge_end->vertex1()->color(color); + } + + if (cell_range.edge_begin->vertex0()->color() == 0) { + // Vertex 0 of edge_end points to the ending endpoint of the input segment (to() or line.b). + VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::HIGH); + cell_range.edge_begin->vertex0()->color(color); + } + } else { + // This could happen when there is a missing Voronoi vertex even after rotation. + assert(cell_range.is_valid()); + } + } + + // FIXME @hejllukas: Implement mapping also for source points and not just for source segments. + } + + // Rotate all Voronoi vertices back. + // When a Voronoi vertex can be mapped to the input segment endpoint, then we don't need to do rotation back. + for (vertex_type &vertex : m_vertices) { + if (vertex.color() == 0) { + // This vertex isn't mapped to any vertex, so we rotate it back. + vertex = VoronoiUtils::make_rotated_vertex(vertex, -fix_angle); + } else { + // This vertex can be mapped to the input segment endpoint. + PointType endpoint = decode_input_segment_endpoint(vertex.color(), segment_begin, segment_end); + vertex_type endpoint_vertex{double(endpoint.x()), double(endpoint.y())}; + endpoint_vertex.incident_edge(vertex.incident_edge()); + endpoint_vertex.color(vertex.color()); + vertex = endpoint_vertex; + } + } + + // We have to clear all marked vertices because some algorithms expect that all vertices have a color equal to 0. + for (vertex_type &vertex : m_vertices) + vertex.color(0); + + m_voronoi_diagram.clear(); + m_is_modified = true; + + return issue_type; } } // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry/Voronoi.hpp b/src/libslic3r/Geometry/Voronoi.hpp index 95b42a263c..1c5d68acd0 100644 --- a/src/libslic3r/Geometry/Voronoi.hpp +++ b/src/libslic3r/Geometry/Voronoi.hpp @@ -140,6 +140,16 @@ public: try_to_repair_degenerated_voronoi_diagram(SegmentIterator segment_begin, SegmentIterator segment_end); private: + struct Segment + { + Point from; + Point to; + + Segment() = delete; + explicit Segment(const Point &from, const Point &to) : from(from), to(to) {} + }; + + void copy_to_local(voronoi_diagram_type &voronoi_diagram); // Detect issues related to Voronoi cells, or that can be detected by iterating over Voronoi cells. // The first type of issue that can be detected is a missing Voronoi vertex, especially when it is @@ -161,8 +171,29 @@ private: bool m_is_modified = false; State m_state = State::UNKNOWN; IssueType m_issue_type = IssueType::UNKNOWN; + +public: + using SegmentIt = std::vector::iterator; + + friend struct boost::polygon::segment_traits; }; } // namespace Slic3r::Geometry +namespace boost::polygon { +template<> struct geometry_concept +{ + typedef segment_concept type; +}; + +template<> struct segment_traits +{ + using coordinate_type = coord_t; + using point_type = Slic3r::Point; + using segment_type = Slic3r::Geometry::VoronoiDiagram::Segment; + + static inline point_type get(const segment_type &segment, direction_1d dir) { return dir.to_int() ? segment.to : segment.from; } +}; +} // namespace boost::polygon + #endif // slic3r_Geometry_Voronoi_hpp_ diff --git a/src/libslic3r/Geometry/VoronoiUtils.cpp b/src/libslic3r/Geometry/VoronoiUtils.cpp index 6348540406..6470fdb753 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.cpp +++ b/src/libslic3r/Geometry/VoronoiUtils.cpp @@ -11,12 +11,15 @@ using ColoredLinesIt = ColoredLines::iterator; // Explicit template instantiation. template LinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, LinesIt, LinesIt); +template VD::SegmentIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); template ColoredLinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); template PolygonsSegmentIndexConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, LinesIt, LinesIt); +template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); template SegmentCellRange VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, LinesIt, LinesIt); +template SegmentCellRange VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); template SegmentCellRange VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); template SegmentCellRange VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); template Points VoronoiUtils::discretize_parabola(const Point &, const Arachne::PolygonsSegmentIndex &, const Point &, const Point &, coord_t, float); @@ -251,4 +254,19 @@ bool VoronoiUtils::is_finite(const VD::vertex_type &vertex) return std::isfinite(vertex.x()) && std::isfinite(vertex.y()); } +VD::vertex_type VoronoiUtils::make_rotated_vertex(VD::vertex_type &vertex, const double angle) +{ + const double cos_a = std::cos(angle); + const double sin_a = std::sin(angle); + + const double rotated_x = (cos_a * vertex.x() - sin_a * vertex.y()); + const double rotated_y = (cos_a * vertex.y() + sin_a * vertex.x()); + + VD::vertex_type rotated_vertex{rotated_x, rotated_y}; + rotated_vertex.incident_edge(vertex.incident_edge()); + rotated_vertex.color(vertex.color()); + + return rotated_vertex; +} + } // namespace Slic3r::Geometry \ No newline at end of file diff --git a/src/libslic3r/Geometry/VoronoiUtils.hpp b/src/libslic3r/Geometry/VoronoiUtils.hpp index d21390772f..02248fdf5e 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.hpp +++ b/src/libslic3r/Geometry/VoronoiUtils.hpp @@ -31,6 +31,8 @@ public: static bool is_finite(const VD::vertex_type &vertex); + static VD::vertex_type make_rotated_vertex(VD::vertex_type &vertex, double angle); + template static typename boost::polygon::enable_if< typename boost::polygon::gtl_if Date: Wed, 31 Jan 2024 17:43:28 +0100 Subject: [PATCH 09/11] SPE-1729: Try to compute the Voronoi diagram again on modified ExPolygon when an invalid Voronoi diagram is produced during the calculation of the medial axis. There are several ExPolygons with very thin lines and holes formed by very close (1-5nm) vertices that are on the edge of our resolution. Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid. So, we filtered out such thin lines and holes and tried to compute the Voronoi diagram again. --- src/libslic3r/Geometry/MedialAxis.cpp | 16 ++++++++ tests/libslic3r/test_voronoi.cpp | 56 +++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/libslic3r/Geometry/MedialAxis.cpp b/src/libslic3r/Geometry/MedialAxis.cpp index 3854dcee48..0e91fea2bb 100644 --- a/src/libslic3r/Geometry/MedialAxis.cpp +++ b/src/libslic3r/Geometry/MedialAxis.cpp @@ -6,6 +6,9 @@ #include "clipper.hpp" #include "VoronoiOffset.hpp" +#include "ClipperUtils.hpp" + +#include #ifdef SLIC3R_DEBUG namespace boost { namespace polygon { @@ -468,6 +471,19 @@ void MedialAxis::build(ThickPolylines* polylines) } #endif // NDEBUG m_vd.construct_voronoi(m_lines.begin(), m_lines.end()); + + // For several ExPolygons in SPE-1729, an invalid Voronoi diagram was produced that wasn't fixable by rotating input data. + // Those ExPolygons contain very thin lines and holes formed by very close (1-5nm) vertices that are on the edge of our resolution. + // Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid. + // So we filter out such thin lines and holes and try to compute the Voronoi diagram again. + if (!m_vd.is_valid()) { + m_lines = to_lines(closing_ex({m_expolygon}, float(2. * SCALED_EPSILON))); + m_vd.construct_voronoi(m_lines.begin(), m_lines.end()); + + if (!m_vd.is_valid()) + BOOST_LOG_TRIVIAL(error) << "MedialAxis - Invalid Voronoi diagram even after morphological closing."; + } + Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines); // static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees // std::vector skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index 7281df7f42..f5fe098818 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -2233,3 +2233,59 @@ TEST_CASE("Non-planar voronoi diagram", "[VoronoiNonPlanar]") // REQUIRE(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(vd)); } + +// This case is extracted from SPE-1729, where several ExPolygon with very thin lines +// and holes formed by very close (1-5nm) vertices that are on the edge of our resolution. +// Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid. +TEST_CASE("Invalid Voronoi diagram - Thin lines - SPE-1729", "[InvalidVoronoiDiagramThinLinesSPE1729]") +{ + Polygon contour = { + {32247689, -2405501}, + {32247733, -2308514}, + {32247692, -2405496}, + {50484384, 332941}, + {50374839, 1052546}, + {32938040, -1637993}, + {32938024, -1673788}, + {32942107, 7220481}, + {32252205, 7447599}, + {32252476, 8037808}, + {32555965, 8277599}, + {17729260, 8904718}, + {17729236, 8853233}, + {17729259, 8904722}, + {17039259, 8935481}, + {17033440, -3880421}, + {17204385, -3852156}, + {17723645, -3441873}, + {17723762, -3187210}, + {17728957, 8240730}, + {17728945, 8213866}, + {31716233, 7614090}, + {20801623, -1009882}, + {21253963, -1580792}, + {32252082, 7157187}, + {32248022, -1673787}, + {24245653, -2925506}, + {18449246, -3809095}, + {18728385, -4449246} + }; + + Polygon hole = { + {32247789, -2181284}, + {32247870, -2003865}, + {32247872, -2003866}, + {32247752, -2267007} + }; + + Polygons polygons = {contour, hole}; + + VD vd; + Lines lines = to_lines(polygons); + vd.construct_voronoi(lines.begin(), lines.end()); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("invalid-voronoi-diagram-thin-lines.svg").c_str(), vd, Points(), lines); +#endif + +// REQUIRE(vd.is_valid()); +} From 24a497e445539885d5627c9173e7241b01d337ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 6 Feb 2024 18:27:28 +0100 Subject: [PATCH 10/11] Make the VoronoiUtils::compute_segment_cell_range() function to take a constant reference to VoronoiDiagram::cell_type instead of a mutable reference. --- .../Arachne/SkeletalTrapezoidation.cpp | 23 ++++++++----------- .../Arachne/SkeletalTrapezoidation.hpp | 10 ++++---- src/libslic3r/Geometry/VoronoiUtils.cpp | 18 +++++++-------- src/libslic3r/Geometry/VoronoiUtils.hpp | 10 ++++---- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 53814c605d..82f86e7a78 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -89,8 +89,7 @@ static void export_graph_to_svg(const std::string } #endif -SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(VD::vertex_type& vd_node, Point p) -{ +SkeletalTrapezoidation::node_t &SkeletalTrapezoidation::makeNode(const VD::vertex_type &vd_node, Point p) { auto he_node_it = vd_node_to_he_node.find(&vd_node); if (he_node_it == vd_node_to_he_node.end()) { @@ -105,8 +104,7 @@ SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(VD::vertex_type } } -void SkeletalTrapezoidation::transferEdge(Point from, Point to, VD::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector& segments) -{ +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) { 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 @@ -328,8 +326,7 @@ Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const st } } -bool SkeletalTrapezoidation::computePointCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, VD::edge_type*& ending_vd_edge, const std::vector& segments) -{ +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. @@ -355,7 +352,7 @@ bool SkeletalTrapezoidation::computePointCellRange(VD::cell_type& cell, Point& s 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 - VD::edge_type* vd_edge = cell.incident_edge(); + 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()) { @@ -429,14 +426,14 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) #endif assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty()); - for (VD::cell_type cell : voronoi_diagram.cells()) { + for (const VD::cell_type &cell : voronoi_diagram.cells()) { if (!cell.incident_edge()) continue; // There is no spoon - Point start_source_point; - Point end_source_point; - VD::edge_type *starting_voronoi_edge = nullptr; - VD::edge_type *ending_voronoi_edge = nullptr; + Point start_source_point; + Point end_source_point; + const VD::edge_type *starting_voronoi_edge = nullptr; + const VD::edge_type *ending_voronoi_edge = nullptr; // Compute and store result in above variables if (cell.contains_point()) { @@ -467,7 +464,7 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) constexpr bool is_next_to_start_or_end = true; graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end); - for (VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) { + for (const VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) { assert(vd_edge->is_finite()); assert(Geometry::VoronoiUtils::is_in_range(*vd_edge)); diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index 9d377e6145..20f7b59186 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -164,9 +164,9 @@ protected: * mapping each voronoi VD edge to the corresponding halfedge HE edge * In case the result segment is discretized, we map the VD edge to the *last* HE edge */ - ankerl::unordered_dense::map vd_edge_to_he_edge; - ankerl::unordered_dense::map vd_node_to_he_node; - node_t& makeNode(VD::vertex_type& vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet. + ankerl::unordered_dense::map vd_edge_to_he_edge; + ankerl::unordered_dense::map vd_node_to_he_node; + node_t &makeNode(const VD::vertex_type &vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet. /*! * (Eventual) returned 'polylines per index' result (from generateToolpaths): @@ -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, VD::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector& segments); + 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); /*! * Discretize a Voronoi edge that represents the medial axis of a vertex- @@ -230,7 +230,7 @@ protected: * /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(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, VD::edge_type*& ending_vd_edge, const std::vector& segments); + 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 diff --git a/src/libslic3r/Geometry/VoronoiUtils.cpp b/src/libslic3r/Geometry/VoronoiUtils.cpp index 6470fdb753..628e39f27d 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.cpp +++ b/src/libslic3r/Geometry/VoronoiUtils.cpp @@ -18,10 +18,10 @@ template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); -template SegmentCellRange VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, LinesIt, LinesIt); -template SegmentCellRange VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); -template SegmentCellRange VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); -template SegmentCellRange VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); +template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, LinesIt, LinesIt); +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 &, ColoredLinesIt, ColoredLinesIt); +template SegmentCellRange VoronoiUtils::compute_segment_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); @@ -195,7 +195,7 @@ typename boost::polygon::enable_if< typename boost::polygon::geometry_concept::value_type>::type>::type>::type, Geometry::SegmentCellRange< typename boost::polygon::segment_point_type::value_type>::type>>::type -VoronoiUtils::compute_segment_cell_range(VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end) +VoronoiUtils::compute_segment_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; @@ -211,10 +211,10 @@ VoronoiUtils::compute_segment_cell_range(VD::cell_type &cell, const SegmentItera SegmentCellRange cell_range(to, from); // Find starting edge and end edge - bool seen_possible_start = false; - bool after_start = false; - bool ending_edge_is_set_before_start = false; - VD::edge_type *edge = cell.incident_edge(); + bool seen_possible_start = false; + bool after_start = false; + bool ending_edge_is_set_before_start = false; + const VD::edge_type *edge = cell.incident_edge(); do { if (edge->is_infinite()) continue; diff --git a/src/libslic3r/Geometry/VoronoiUtils.hpp b/src/libslic3r/Geometry/VoronoiUtils.hpp index 02248fdf5e..577841e75c 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.hpp +++ b/src/libslic3r/Geometry/VoronoiUtils.hpp @@ -11,10 +11,10 @@ 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. - VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts. - VD::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends. + 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 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) @@ -92,7 +92,7 @@ public: typename boost::polygon::geometry_concept::value_type>::type>::type>::type, Geometry::SegmentCellRange< typename boost::polygon::segment_point_type::value_type>::type>>::type - compute_segment_cell_range(VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + compute_segment_cell_range(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); template static bool is_in_range(double value) { From 76435e7add9f93a6d5fafcc1c0336b14da6505bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 6 Feb 2024 18:47:34 +0100 Subject: [PATCH 11/11] SPE-1840: Rework multi-material segmentation to work directly on the Voronoi diagram without creating a copy of it. Previous algorithms assume that they can get an invalid Voronoi diagram. Because of that, during the multi-material segmentation, a copy of the Voronoi diagram was created, and there were several attempts to fix missing vertices and edges. But as it shows, this wasn't a good enough approach and sometimes led to several issues like bleeding layers. After generalization, our approach for detection and repairs of invalid Voronoi diagrams from Arachne, we could assume that multi-material segmentation gets non-invalid Voronoi diagrams. With this assumption, we reimplement multi-materials segmentation to work directly on the Voronoi diagram. That should make multi-material segmentation more stable. So, this should fix several issues like bleeding layers. Also, memory consumption should decrease by a lot. Also, there should be some speedup of multi-materials segmentation. --- src/libslic3r/Format/3mf.cpp | 28 +- src/libslic3r/Geometry/Voronoi.cpp | 4 +- src/libslic3r/Geometry/VoronoiUtils.cpp | 13 +- src/libslic3r/Geometry/VoronoiUtils.hpp | 2 + src/libslic3r/Geometry/VoronoiUtilsCgal.cpp | 4 +- src/libslic3r/Model.cpp | 8 +- src/libslic3r/Model.hpp | 36 +- src/libslic3r/MultiMaterialSegmentation.cpp | 1267 ++++++----------- src/libslic3r/PrintApply.cpp | 6 +- src/libslic3r/PrintObjectSlice.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 4 +- src/slic3r/GUI/GUI_ObjectList.cpp | 4 +- src/slic3r/GUI/GUI_Preview.cpp | 2 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 4 +- src/slic3r/GUI/Plater.cpp | 6 +- 15 files changed, 468 insertions(+), 922 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 34f2eee4e1..dd9fab6123 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -124,7 +124,7 @@ static constexpr const char* PRINTABLE_ATTR = "printable"; static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; -static constexpr const char* MMU_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation"; +static constexpr const char* MM_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation"; static constexpr const char* KEY_ATTR = "key"; static constexpr const char* VALUE_ATTR = "value"; @@ -362,7 +362,7 @@ namespace Slic3r { std::vector triangles; std::vector custom_supports; std::vector custom_seam; - std::vector mmu_segmentation; + std::vector mm_segmentation; bool empty() { return vertices.empty() || triangles.empty(); } @@ -371,7 +371,7 @@ namespace Slic3r { triangles.clear(); custom_supports.clear(); custom_seam.clear(); - mmu_segmentation.clear(); + mm_segmentation.clear(); } }; @@ -1830,7 +1830,7 @@ namespace Slic3r { m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); - m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR)); + m_curr_object.geometry.mm_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MM_SEGMENTATION_ATTR)); return true; } @@ -2320,25 +2320,25 @@ namespace Slic3r { if (has_transform) volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); - // recreate custom supports, seam and mmu segmentation from previously loaded attribute + // recreate custom supports, seam and mm segmentation from previously loaded attribute volume->supported_facets.reserve(triangles_count); volume->seam_facets.reserve(triangles_count); - volume->mmu_segmentation_facets.reserve(triangles_count); + volume->mm_segmentation_facets.reserve(triangles_count); for (size_t i=0; isupported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); if (! geometry.custom_seam[index].empty()) volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); - if (! geometry.mmu_segmentation[index].empty()) - volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); + if (! geometry.mm_segmentation[index].empty()) + volume->mm_segmentation_facets.set_triangle_from_string(i, geometry.mm_segmentation[index]); } volume->supported_facets.shrink_to_fit(); volume->seam_facets.shrink_to_fit(); - volume->mmu_segmentation_facets.shrink_to_fit(); + volume->mm_segmentation_facets.shrink_to_fit(); if (auto &es = volume_data.shape_configuration; es.has_value()) volume->emboss_shape = std::move(es); @@ -3002,12 +3002,12 @@ namespace Slic3r { output_buffer += "\""; } - std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i); - if (! mmu_painting_data_string.empty()) { + std::string mm_painting_data_string = volume->mm_segmentation_facets.get_triangle_as_string(i); + if (! mm_painting_data_string.empty()) { output_buffer += " "; - output_buffer += MMU_SEGMENTATION_ATTR; + output_buffer += MM_SEGMENTATION_ATTR; output_buffer += "=\""; - output_buffer += mmu_painting_data_string; + output_buffer += mm_painting_data_string; output_buffer += "\""; } diff --git a/src/libslic3r/Geometry/Voronoi.cpp b/src/libslic3r/Geometry/Voronoi.cpp index 404c5c7634..e1df7322a4 100644 --- a/src/libslic3r/Geometry/Voronoi.cpp +++ b/src/libslic3r/Geometry/Voronoi.cpp @@ -11,11 +11,11 @@ namespace Slic3r::Geometry { using PolygonsSegmentIndexConstIt = std::vector::const_iterator; using LinesIt = Lines::iterator; -using ColoredLinesIt = ColoredLines::iterator; +using ColoredLinesConstIt = ColoredLines::const_iterator; // Explicit template instantiation. template void VoronoiDiagram::construct_voronoi(LinesIt, LinesIt, bool); -template void VoronoiDiagram::construct_voronoi(ColoredLinesIt, ColoredLinesIt, bool); +template void VoronoiDiagram::construct_voronoi(ColoredLinesConstIt, ColoredLinesConstIt, bool); template void VoronoiDiagram::construct_voronoi(PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt, bool); template diff --git a/src/libslic3r/Geometry/VoronoiUtils.cpp b/src/libslic3r/Geometry/VoronoiUtils.cpp index 628e39f27d..fae30e0f98 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.cpp +++ b/src/libslic3r/Geometry/VoronoiUtils.cpp @@ -8,19 +8,22 @@ namespace Slic3r::Geometry { using PolygonsSegmentIndexConstIt = std::vector::const_iterator; using LinesIt = Lines::iterator; using ColoredLinesIt = ColoredLines::iterator; +using ColoredLinesConstIt = ColoredLines::const_iterator; // Explicit template instantiation. template LinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, LinesIt, LinesIt); template VD::SegmentIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); template ColoredLinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); +template ColoredLinesConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt); template PolygonsSegmentIndexConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, LinesIt, LinesIt); template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt); +template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt); template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, LinesIt, LinesIt); 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 &, ColoredLinesIt, ColoredLinesIt); +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 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); @@ -241,7 +244,13 @@ VoronoiUtils::compute_segment_cell_range(const VD::cell_type &cell, const Segmen Vec2i64 VoronoiUtils::to_point(const VD::vertex_type *vertex) { - const double x = vertex->x(), y = vertex->y(); + assert(vertex != nullptr); + return VoronoiUtils::to_point(*vertex); +} + +Vec2i64 VoronoiUtils::to_point(const VD::vertex_type &vertex) +{ + const double x = vertex.x(), y = vertex.y(); assert(std::isfinite(x) && std::isfinite(y)); assert(is_in_range(x) && is_in_range(y)); diff --git a/src/libslic3r/Geometry/VoronoiUtils.hpp b/src/libslic3r/Geometry/VoronoiUtils.hpp index 577841e75c..bf63914677 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.hpp +++ b/src/libslic3r/Geometry/VoronoiUtils.hpp @@ -29,6 +29,8 @@ class VoronoiUtils public: static Vec2i64 to_point(const VD::vertex_type *vertex); + static Vec2i64 to_point(const VD::vertex_type &vertex); + static bool is_finite(const VD::vertex_type &vertex); static VD::vertex_type make_rotated_vertex(VD::vertex_type &vertex, double angle); diff --git a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp index e14499facd..e80f1a5a88 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp @@ -19,12 +19,12 @@ namespace Slic3r::Geometry { using PolygonsSegmentIndexConstIt = std::vector::const_iterator; using LinesIt = Lines::iterator; -using ColoredLinesIt = ColoredLines::iterator; +using ColoredLinesConstIt = ColoredLines::const_iterator; // Explicit template instantiation. template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, LinesIt, LinesIt); template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, VD::SegmentIt, VD::SegmentIt); -template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, ColoredLinesIt, ColoredLinesIt); +template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, ColoredLinesConstIt, ColoredLinesConstIt); template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); // The tangent vector of the parabola is computed based on the Proof of the reflective property. diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 9d061dfef1..c7c53cf55d 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1237,7 +1237,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con vol->supported_facets.assign(volume->supported_facets); vol->seam_facets.assign(volume->seam_facets); - vol->mmu_segmentation_facets.assign(volume->mmu_segmentation_facets); + vol->mm_segmentation_facets.assign(volume->mm_segmentation_facets); // Perform conversion only if the target "imperial" state is different from the current one. // This check supports conversion of "mixed" set of volumes, each with different "imperial" state. @@ -1349,7 +1349,7 @@ void ModelVolume::reset_extra_facets() { this->supported_facets.reset(); this->seam_facets.reset(); - this->mmu_segmentation_facets.reset(); + this->mm_segmentation_facets.reset(); } @@ -1915,7 +1915,7 @@ void ModelVolume::assign_new_unique_ids_recursive() config.set_new_unique_id(); supported_facets.set_new_unique_id(); seam_facets.set_new_unique_id(); - mmu_segmentation_facets.set_new_unique_id(); + mm_segmentation_facets.set_new_unique_id(); } void ModelVolume::rotate(double angle, Axis axis) @@ -2224,7 +2224,7 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec { return model_property_changed(mo, mo_new, [](const ModelVolumeType t) { return t == ModelVolumeType::MODEL_PART; }, - [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mmu_segmentation_facets.timestamp_matches(mv_new.mmu_segmentation_facets); }); + [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mm_segmentation_facets.timestamp_matches(mv_new.mm_segmentation_facets); }); } bool model_has_parameter_modifiers_in_objects(const Model &model) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 5afd77883e..c885b78e56 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -823,8 +823,8 @@ public: // List of seam enforcers/blockers. FacetsAnnotation seam_facets; - // List of mesh facets painted for MMU segmentation. - FacetsAnnotation mmu_segmentation_facets; + // List of mesh facets painted for MM segmentation. + FacetsAnnotation mm_segmentation_facets; // Is set only when volume is Embossed Text type // Contain information how to re-create volume @@ -929,12 +929,12 @@ public: this->config.set_new_unique_id(); this->supported_facets.set_new_unique_id(); this->seam_facets.set_new_unique_id(); - this->mmu_segmentation_facets.set_new_unique_id(); + this->mm_segmentation_facets.set_new_unique_id(); } bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } bool is_seam_painted() const { return !this->seam_facets.empty(); } - bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } + bool is_mm_painted() const { return !this->mm_segmentation_facets.empty(); } protected: friend class Print; @@ -973,11 +973,11 @@ private: assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); + assert(this->mm_segmentation_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); + assert(this->id() != this->mm_segmentation_facets.id()); return true; } @@ -1003,23 +1003,23 @@ private: ObjectBase(other), name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), - supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets), + supported_facets(other.supported_facets), seam_facets(other.seam_facets), mm_segmentation_facets(other.mm_segmentation_facets), cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape) { assert(this->id().valid()); assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); + assert(this->mm_segmentation_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); + assert(this->id() != this->mm_segmentation_facets.id()); assert(this->id() == other.id()); assert(this->config.id() == other.config.id()); assert(this->supported_facets.id() == other.supported_facets.id()); assert(this->seam_facets.id() == other.seam_facets.id()); - assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id()); + assert(this->mm_segmentation_facets.id() == other.mm_segmentation_facets.id()); this->set_material_id(other.material_id()); } // Providing a new mesh, therefore this volume will get a new unique ID assigned. @@ -1031,11 +1031,11 @@ private: assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); + assert(this->mm_segmentation_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); + assert(this->id() != this->mm_segmentation_facets.id()); assert(this->id() != other.id()); assert(this->config.id() == other.config.id()); this->set_material_id(other.material_id()); @@ -1046,11 +1046,11 @@ private: assert(this->config.id() != other.config.id()); assert(this->supported_facets.id() != other.supported_facets.id()); assert(this->seam_facets.id() != other.seam_facets.id()); - assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id()); + assert(this->mm_segmentation_facets.id() != other.mm_segmentation_facets.id()); assert(this->id() != this->config.id()); assert(this->supported_facets.empty()); assert(this->seam_facets.empty()); - assert(this->mmu_segmentation_facets.empty()); + assert(this->mm_segmentation_facets.empty()); } ModelVolume& operator=(ModelVolume &rhs) = delete; @@ -1058,19 +1058,19 @@ private: friend class cereal::access; friend class UndoRedo::StackImpl; // Used for deserialization, therefore no IDs are allocated. - ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) { + ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mm_segmentation_facets(-1), object(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); assert(this->supported_facets.id().invalid()); assert(this->seam_facets.id().invalid()); - assert(this->mmu_segmentation_facets.id().invalid()); + assert(this->mm_segmentation_facets.id().invalid()); } template void load(Archive &ar) { bool has_convex_hull; ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info); cereal::load_by_value(ar, supported_facets); cereal::load_by_value(ar, seam_facets); - cereal::load_by_value(ar, mmu_segmentation_facets); + cereal::load_by_value(ar, mm_segmentation_facets); cereal::load_by_value(ar, config); cereal::load(ar, text_configuration); cereal::load(ar, emboss_shape); @@ -1088,7 +1088,7 @@ private: ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info); cereal::save_by_value(ar, supported_facets); cereal::save_by_value(ar, seam_facets); - cereal::save_by_value(ar, mmu_segmentation_facets); + cereal::save_by_value(ar, mm_segmentation_facets); cereal::save_by_value(ar, config); cereal::save(ar, text_configuration); cereal::save(ar, emboss_shape); diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index e40c081066..708bdcf5ee 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -8,11 +8,11 @@ #include "Layer.hpp" #include "Print.hpp" #include "Geometry/VoronoiVisualUtils.hpp" +#include "Geometry/VoronoiUtils.hpp" #include "MutablePolygon.hpp" #include "format.hpp" #include -#include #include #include @@ -20,13 +20,19 @@ #include #include -#include +//#define MM_SEGMENTATION_DEBUG_GRAPH +//#define MM_SEGMENTATION_DEBUG_REGIONS +//#define MM_SEGMENTATION_DEBUG_INPUT +//#define MM_SEGMENTATION_DEBUG_PAINTED_LINES +//#define MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS -//#define MMU_SEGMENTATION_DEBUG_GRAPH -//#define MMU_SEGMENTATION_DEBUG_REGIONS -//#define MMU_SEGMENTATION_DEBUG_INPUT -//#define MMU_SEGMENTATION_DEBUG_PAINTED_LINES -//#define MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS +#if defined(MM_SEGMENTATION_DEBUG_GRAPH) || defined(MM_SEGMENTATION_DEBUG_REGIONS) || \ + defined(MM_SEGMENTATION_DEBUG_INPUT) || defined(MM_SEGMENTATION_DEBUG_PAINTED_LINES) || \ + defined(MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS) +#define MM_SEGMENTATION_DEBUG +#endif + +//#define MM_SEGMENTATION_DEBUG_TOP_BOTTOM namespace Slic3r { @@ -130,55 +136,31 @@ struct PaintedLineVisitor static inline const double append_threshold2 = Slic3r::sqr(append_threshold); }; -static Polygon colored_points_to_polygon(const std::vector &lines) -{ - Polygon out; - out.points.reserve(lines.size()); - for (const ColoredLine &l : lines) - out.points.emplace_back(l.line.a); - return out; -} - -static Polygons colored_points_to_polygon(const std::vector> &lines) -{ - Polygons out; - out.reserve(lines.size()); - for (const std::vector &l : lines) - out.emplace_back(colored_points_to_polygon(l)); - return out; +BoundingBox get_extents(const std::vector &colored_polygons) { + BoundingBox bbox; + for (const ColoredLines &colored_lines : colored_polygons) { + for (const ColoredLine &colored_line : colored_lines) { + bbox.merge(colored_line.line.a); + bbox.merge(colored_line.line.b); + } + } + return bbox; } // Flatten the vector of vectors into a vector. -static inline std::vector to_lines(const std::vector> &c_lines) +static inline ColoredLines to_lines(const std::vector &c_lines) { size_t n_lines = 0; for (const auto &c_line : c_lines) n_lines += c_line.size(); - std::vector lines; + ColoredLines lines; lines.reserve(n_lines); for (const auto &c_line : c_lines) lines.insert(lines.end(), c_line.begin(), c_line.end()); return lines; } -static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const Vec2d &ipt) -{ - // Convert ipt to doubles, force the 80bit FPU temporary to 64bit and then compare. - // This should work with any settings of math compiler switches and the C++ compiler - // shall understand the memcpies as type punning and it shall optimize them out. - using ulp_cmp_type = boost::polygon::detail::ulp_comparison; - ulp_cmp_type ulp_cmp; - static constexpr int ULPS = boost::polygon::voronoi_diagram_traits::vertex_equality_predicate_type::ULPS; - return ulp_cmp(vertex.x(), ipt.x(), ULPS) == ulp_cmp_type::EQUAL && - ulp_cmp(vertex.y(), ipt.y(), ULPS) == ulp_cmp_type::EQUAL; -} - -static inline bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Vec2d &ipt) -{ - return vertex_equal_to_point(*vertex, ipt); -} - -static std::vector> get_segments(const std::vector &polygon) +static std::vector> get_segments(const ColoredLines &polygon) { std::vector> segments; @@ -203,16 +185,6 @@ static std::vector> get_segments(const std::vector>> get_all_segments(const std::vector> &color_poly) -{ - std::vector>> all_segments(color_poly.size()); - for (size_t poly_idx = 0; poly_idx < color_poly.size(); ++poly_idx) { - const std::vector &c_polygon = color_poly[poly_idx]; - all_segments[poly_idx] = get_segments(c_polygon); - } - return all_segments; -} - static std::vector filter_painted_lines(const Line &line_to_process, const size_t start_idx, const size_t end_idx, const std::vector &painted_lines) { const int filter_eps_value = scale_(0.1f); @@ -293,7 +265,7 @@ static std::vector> post_process_painted_lines(const st } #ifndef NDEBUG -static bool are_lines_connected(const std::vector &colored_lines) +static bool are_lines_connected(const ColoredLines &colored_lines) { for (size_t line_idx = 1; line_idx < colored_lines.size(); ++line_idx) if (colored_lines[line_idx - 1].line.b != colored_lines[line_idx].line.a) @@ -302,7 +274,7 @@ static bool are_lines_connected(const std::vector &colored_lines) } #endif -static std::vector colorize_line(const Line &line_to_process, +static ColoredLines colorize_line(const Line &line_to_process, const size_t start_idx, const size_t end_idx, const std::vector &painted_contour) @@ -310,9 +282,9 @@ static std::vector colorize_line(const Line &line_to_process, assert(start_idx < painted_contour.size() && end_idx < painted_contour.size() && start_idx <= end_idx); assert(std::all_of(painted_contour.begin() + start_idx, painted_contour.begin() + end_idx + 1, [&painted_contour, &start_idx](const auto &p_line) { return painted_contour[start_idx].line_idx == p_line.line_idx; })); - const int filter_eps_value = scale_(0.1f); - std::vector final_lines; - const PaintedLine &first_line = painted_contour[start_idx]; + const int filter_eps_value = scale_(0.1f); + ColoredLines final_lines; + const PaintedLine &first_line = painted_contour[start_idx]; if (double dist_to_start = (first_line.projected_line.a - line_to_process.a).cast().norm(); dist_to_start > filter_eps_value) final_lines.push_back({Line(line_to_process.a, first_line.projected_line.a), 0}); final_lines.push_back({first_line.projected_line, first_line.color}); @@ -351,7 +323,7 @@ static std::vector colorize_line(const Line &line_to_process, if (line_1.line.length() <= scale_(0.2)) line_1.color = line_0.color; } - std::vector colored_lines_simple; + ColoredLines colored_lines_simple; colored_lines_simple.emplace_back(final_lines.front()); for (size_t line_idx = 1; line_idx < final_lines.size(); ++line_idx) { const ColoredLine &line_0 = final_lines[line_idx]; @@ -379,7 +351,7 @@ static std::vector colorize_line(const Line &line_to_process, return final_lines; } -static std::vector filter_colorized_polygon(std::vector &&new_lines) { +static ColoredLines filter_colorized_polygon(ColoredLines &&new_lines) { for (size_t line_idx = 2; line_idx < new_lines.size(); ++line_idx) { const ColoredLine &line_0 = new_lines[line_idx - 2]; ColoredLine &line_1 = new_lines[line_idx - 1]; @@ -468,10 +440,10 @@ static std::vector filter_colorized_polygon(std::vector colorize_contour(const EdgeGrid::Contour &contour, const std::vector &painted_contour) { +static ColoredLines colorize_contour(const EdgeGrid::Contour &contour, const std::vector &painted_contour) { assert(painted_contour.empty() || std::all_of(painted_contour.begin(), painted_contour.end(), [&painted_contour](const auto &p_line) { return painted_contour.front().contour_idx == p_line.contour_idx; })); - std::vector colorized_contour; + ColoredLines colorized_contour; if (painted_contour.empty()) { // Appends contour with default color for lines before the first PaintedLine. colorized_contour.reserve(contour.num_segments()); @@ -508,297 +480,33 @@ static std::vector colorize_contour(const EdgeGrid::Contour &contou return filter_colorized_polygon(std::move(colorized_contour)); } -static std::vector> colorize_contours(const std::vector &contours, const std::vector> &painted_contours) +static std::vector colorize_contours(const std::vector &contours, const std::vector> &painted_contours) { assert(contours.size() == painted_contours.size()); - std::vector> colorized_contours(contours.size()); + std::vector colorized_contours(contours.size()); for (const std::vector &painted_contour : painted_contours) { size_t contour_idx = &painted_contour - &painted_contours.front(); colorized_contours[contour_idx] = colorize_contour(contours[contour_idx], painted_contours[contour_idx]); } + + size_t poly_idx = 0; + for (ColoredLines &color_lines : colorized_contours) { + size_t line_idx = 0; + for (size_t color_line_idx = 0; color_line_idx < color_lines.size(); ++color_line_idx) { + color_lines[color_line_idx].poly_idx = int(poly_idx); + color_lines[color_line_idx].local_line_idx = int(line_idx); + ++line_idx; + } + ++poly_idx; + } + return colorized_contours; } -using boost::polygon::voronoi_diagram; - -static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return {coord_t(point->x()), coord_t(point->y())}; } - -static inline Point mk_point(const Voronoi::Internal::point_type &point) { return {coord_t(point.x()), coord_t(point.y())}; } - -static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return {coord_t(point.x()), coord_t(point.y())}; } - -static inline Point mk_point(const Vec2d &point) { return {coord_t(std::round(point.x())), coord_t(std::round(point.y()))}; } - -static inline Vec2d mk_vec2(const voronoi_diagram::vertex_type *point) { return {point->x(), point->y()}; } - -struct MMU_Graph -{ - enum class ARC_TYPE { BORDER, NON_BORDER }; - - struct Arc - { - size_t from_idx; - size_t to_idx; - int color; - ARC_TYPE type; - - bool operator==(const Arc &rhs) const { return (from_idx == rhs.from_idx) && (to_idx == rhs.to_idx) && (color == rhs.color) && (type == rhs.type); } - bool operator!=(const Arc &rhs) const { return !operator==(rhs); } - }; - - struct Node - { - Vec2d point; - std::list arc_idxs; - - void remove_edge(const size_t to_idx, MMU_Graph &graph) - { - for (auto arc_it = this->arc_idxs.begin(); arc_it != this->arc_idxs.end(); ++arc_it) { - MMU_Graph::Arc &arc = graph.arcs[*arc_it]; - if (arc.to_idx == to_idx) { - assert(arc.type != ARC_TYPE::BORDER); - this->arc_idxs.erase(arc_it); - break; - } - } - } - }; - - std::vector nodes; - std::vector arcs; - size_t all_border_points{}; - - std::vector polygon_idx_offset; - std::vector polygon_sizes; - - void remove_edge(const size_t from_idx, const size_t to_idx) - { - nodes[from_idx].remove_edge(to_idx, *this); - nodes[to_idx].remove_edge(from_idx, *this); - } - - [[nodiscard]] size_t get_global_index(const size_t poly_idx, const size_t point_idx) const { return polygon_idx_offset[poly_idx] + point_idx; } - - void append_edge(const size_t &from_idx, const size_t &to_idx, int color = -1, ARC_TYPE type = ARC_TYPE::NON_BORDER) - { - // Don't append duplicate edges between the same nodes. - for (const size_t &arc_idx : this->nodes[from_idx].arc_idxs) - if (arcs[arc_idx].to_idx == to_idx) - return; - for (const size_t &arc_idx : this->nodes[to_idx].arc_idxs) - if (arcs[arc_idx].to_idx == from_idx) - return; - - this->nodes[from_idx].arc_idxs.push_back(this->arcs.size()); - this->arcs.push_back({from_idx, to_idx, color, type}); - - // Always insert only one directed arc for the input polygons. - // Two directed arcs in both directions are inserted if arcs aren't between points of the input polygons. - if (type == ARC_TYPE::NON_BORDER) { - this->nodes[to_idx].arc_idxs.push_back(this->arcs.size()); - this->arcs.push_back({to_idx, from_idx, color, type}); - } - } - - // It assumes that between points of the input polygons is always only one directed arc, - // with the same direction as lines of the input polygon. - [[nodiscard]] MMU_Graph::Arc get_border_arc(size_t idx) const { - assert(idx < this->all_border_points); - return this->arcs[idx]; - } - - [[nodiscard]] size_t nodes_count() const { return this->nodes.size(); } - - void remove_nodes_with_one_arc() - { - std::queue update_queue; - for (const MMU_Graph::Node &node : this->nodes) { - size_t node_idx = &node - &this->nodes.front(); - // Skip nodes that represent points of input polygons. - if (node.arc_idxs.size() == 1 && node_idx >= this->all_border_points) - update_queue.emplace(&node - &this->nodes.front()); - } - - while (!update_queue.empty()) { - size_t node_from_idx = update_queue.front(); - MMU_Graph::Node &node_from = this->nodes[update_queue.front()]; - update_queue.pop(); - if (node_from.arc_idxs.empty()) - continue; - - assert(node_from.arc_idxs.size() == 1); - size_t node_to_idx = arcs[node_from.arc_idxs.front()].to_idx; - MMU_Graph::Node &node_to = this->nodes[node_to_idx]; - this->remove_edge(node_from_idx, node_to_idx); - if (node_to.arc_idxs.size() == 1 && node_to_idx >= this->all_border_points) - update_queue.emplace(node_to_idx); - } - } - - void add_contours(const std::vector> &color_poly) - { - this->all_border_points = nodes.size(); - this->polygon_sizes = std::vector(color_poly.size()); - for (size_t polygon_idx = 0; polygon_idx < color_poly.size(); ++polygon_idx) - this->polygon_sizes[polygon_idx] = color_poly[polygon_idx].size(); - this->polygon_idx_offset = std::vector(color_poly.size()); - this->polygon_idx_offset[0] = 0; - for (size_t polygon_idx = 1; polygon_idx < color_poly.size(); ++polygon_idx) { - this->polygon_idx_offset[polygon_idx] = this->polygon_idx_offset[polygon_idx - 1] + color_poly[polygon_idx - 1].size(); - } - - size_t poly_idx = 0; - for (const std::vector &color_lines : color_poly) { - size_t line_idx = 0; - for (const ColoredLine &color_line : color_lines) { - size_t from_idx = this->get_global_index(poly_idx, line_idx); - size_t to_idx = this->get_global_index(poly_idx, (line_idx + 1) % color_lines.size()); - this->append_edge(from_idx, to_idx, color_line.color, ARC_TYPE::BORDER); - ++line_idx; - } - ++poly_idx; - } - } - - // Nodes 0..all_border_points are only one with are on countour. Other vertexis are consider as not on coouter. So we check if base on attach index - inline bool is_vertex_on_contour(const Voronoi::VD::vertex_type *vertex) const - { - assert(vertex != nullptr); - return vertex->color() < this->all_border_points; - } - - [[nodiscard]] inline bool is_edge_attach_to_contour(const voronoi_diagram::const_edge_iterator &edge_iterator) const - { - return this->is_vertex_on_contour(edge_iterator->vertex0()) || this->is_vertex_on_contour(edge_iterator->vertex1()); - } - - [[nodiscard]] inline bool is_edge_connecting_two_contour_vertices(const voronoi_diagram::const_edge_iterator &edge_iterator) const - { - return this->is_vertex_on_contour(edge_iterator->vertex0()) && this->is_vertex_on_contour(edge_iterator->vertex1()); - } - - // All Voronoi vertices are post-processes to merge very close vertices to single. Witch eliminates issues with intersection edges. - // Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them. - void append_voronoi_vertices(const Voronoi::VD &vd, const Polygons &color_poly_tmp, BoundingBox bbox) { - bbox.offset(SCALED_EPSILON); - - struct CPoint - { - CPoint() = delete; - CPoint(const Vec2d &point, size_t contour_idx, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(contour_idx) {} - CPoint(const Vec2d &point, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(0) {} - const Vec2d m_point_double; - const Point m_point; - size_t m_point_idx; - size_t m_contour_idx; - - [[nodiscard]] const Vec2d &point_double() const { return m_point_double; } - [[nodiscard]] const Point &point() const { return m_point; } - bool operator==(const CPoint &rhs) const { return m_point_double == rhs.m_point_double && m_contour_idx == rhs.m_contour_idx && m_point_idx == rhs.m_point_idx; } - }; - struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }}; - typedef ClosestPointInRadiusLookup CPointLookupType; - - CPointLookupType closest_voronoi_point(coord_t(SCALED_EPSILON)); - CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON)); - for (const Polygon &polygon : color_poly_tmp) - for (const Point &pt : polygon.points) - closest_contour_point.insert(CPoint(Vec2d(pt.x(), pt.y()), &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front())); - - for (const voronoi_diagram::vertex_type &vertex : vd.vertices()) { - vertex.color(-1); - Vec2d vertex_point_double = Vec2d(vertex.x(), vertex.y()); - Point vertex_point = mk_point(vertex); - - const Vec2d &first_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point; - const Vec2d &second_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point; - - if (vertex_equal_to_point(&vertex, first_point_double)) { - assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); - assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); - vertex.color(this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx); - } else if (vertex_equal_to_point(&vertex, second_point_double)) { - assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); - assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); - vertex.color(this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx); - } else if (bbox.contains(vertex_point)) { - if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < Slic3r::sqr(3 * SCALED_EPSILON)) { - vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx)); - } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(SCALED_EPSILON / 10.0)) { - closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count())); - vertex.color(this->nodes_count()); - this->nodes.push_back({vertex_point_double}); - } else { - // Boost Voronoi diagram generator sometimes creates two very closed points instead of one point. - // For the example points (146872.99999999997, -146872.99999999997) and (146873, -146873), this example also included in Voronoi generator test cases. - std::vector> all_closes_c_points = closest_voronoi_point.find_all(vertex_point); - int merge_to_point = -1; - for (const std::pair &c_point : all_closes_c_points) - if ((vertex_point_double - c_point.first->point_double()).squaredNorm() <= Slic3r::sqr(EPSILON)) { - merge_to_point = int(c_point.first->m_point_idx); - break; - } - - if (merge_to_point != -1) { - vertex.color(merge_to_point); - } else { - closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count())); - vertex.color(this->nodes_count()); - this->nodes.push_back({vertex_point_double}); - } - } - } - } - } - - void garbage_collect() - { - std::vector nodes_map(this->nodes.size(), -1); - int nodes_count = 0; - size_t arcs_count = 0; - for (const MMU_Graph::Node &node : this->nodes) - if (size_t node_idx = &node - &this->nodes.front(); !node.arc_idxs.empty()) { - nodes_map[node_idx] = nodes_count++; - arcs_count += node.arc_idxs.size(); - } - - std::vector new_nodes; - std::vector new_arcs; - new_nodes.reserve(nodes_count); - new_arcs.reserve(arcs_count); - for (const MMU_Graph::Node &node : this->nodes) - if (size_t node_idx = &node - &this->nodes.front(); nodes_map[node_idx] >= 0) { - new_nodes.push_back({node.point}); - for (const size_t &arc_idx : node.arc_idxs) { - const Arc &arc = this->arcs[arc_idx]; - new_nodes.back().arc_idxs.emplace_back(new_arcs.size()); - new_arcs.push_back({size_t(nodes_map[arc.from_idx]), size_t(nodes_map[arc.to_idx]), arc.color, arc.type}); - } - } - - this->nodes = std::move(new_nodes); - this->arcs = std::move(new_arcs); - } -}; - -static inline void mark_processed(const voronoi_diagram::const_edge_iterator &edge_iterator) -{ - edge_iterator->color(true); - edge_iterator->twin()->color(true); -} - -// Return true, if "p" is closer to line.a, then line.b -static inline bool is_point_closer_to_beginning_of_line(const Line &line, const Point &p) -{ - return (p - line.a).cast().squaredNorm() < (p - line.b).cast().squaredNorm(); -} - -static inline bool has_same_color(const ColoredLine &cl1, const ColoredLine &cl2) { return cl1.color == cl2.color; } - // Determines if the line points from the point between two contour lines is pointing inside polygon or outside. static inline bool points_inside(const Line &contour_first, const Line &contour_second, const Point &new_point) { - // Used in points_inside for decision if line leading thought the common point of two lines is pointing inside polygon or outside + // TODO: Used in points_inside for decision if line leading thought the common point of two lines is pointing inside polygon or outside auto three_points_inward_normal = [](const Point &left, const Point &middle, const Point &right) -> Vec2d { assert(left != middle); assert(middle != right); @@ -813,440 +521,309 @@ static inline bool points_inside(const Line &contour_first, const Line &contour_ return side > 0.; } -static inline bool line_intersection_with_epsilon(const Line &line_to_extend, const Line &other, Point *intersection) -{ - Line extended_line = line_to_extend; - extended_line.extend(15 * SCALED_EPSILON); - return extended_line.intersection(other, intersection); +enum VD_ANNOTATION : Voronoi::VD::cell_type::color_type { + VERTEX_ON_CONTOUR = 1, + DELETED = 2 +}; + +#ifdef MM_SEGMENTATION_DEBUG_GRAPH +static void export_graph_to_svg(const std::string &path, const Voronoi::VD& vd, const std::vector& colored_polygons) { + const coordf_t stroke_width = scaled(0.05f); + const BoundingBox bbox = get_extents(colored_polygons); + + SVG svg(path.c_str(), bbox); + for (const ColoredLines &colored_lines : colored_polygons) + for (const ColoredLine &colored_line : colored_lines) + svg.draw(colored_line.line, "black", stroke_width); + + for (const Voronoi::VD::vertex_type &vertex : vd.vertices()) { + if (Geometry::VoronoiUtils::is_in_range(vertex)) { + if (const Point pt = Geometry::VoronoiUtils::to_point(&vertex).cast(); vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR) { + svg.draw(pt, "blue", coord_t(stroke_width)); + } else if (vertex.color() != VD_ANNOTATION::DELETED) { + svg.draw(pt, "green", coord_t(stroke_width)); + } + } + } + + for (const Voronoi::VD::edge_type &edge : vd.edges()) { + if (edge.is_infinite() || !Geometry::VoronoiUtils::is_in_range(edge)) + continue; + + const Point from = Geometry::VoronoiUtils::to_point(edge.vertex0()).cast(); + const Point to = Geometry::VoronoiUtils::to_point(edge.vertex1()).cast(); + + if (edge.color() != VD_ANNOTATION::DELETED) + svg.draw(Line(from, to), "red", stroke_width); + } +} +#endif // MM_SEGMENTATION_DEBUG_GRAPH + +static size_t non_deleted_edge_count(const VD::vertex_type &vertex) { + size_t non_deleted_edge_cnt = 0; + const VD::edge_type *edge = vertex.incident_edge(); + do { + if (edge->color() != VD_ANNOTATION::DELETED) + ++non_deleted_edge_cnt; + } while (edge = edge->prev()->twin(), edge != vertex.incident_edge()); + + return non_deleted_edge_cnt; } -// For every ColoredLine in lines_colored_out, assign the index of the polygon to which belongs and also the index of this line inside of the polygon. -static inline void init_polygon_indices(const MMU_Graph &graph, - const std::vector> &color_poly, - std::vector &lines_colored_out) -{ - size_t poly_idx = 0; - for (const std::vector &color_lines : color_poly) { - size_t line_idx = 0; - for (size_t color_line_idx = 0; color_line_idx < color_lines.size(); ++color_line_idx) { - size_t from_idx = graph.get_global_index(poly_idx, line_idx); - lines_colored_out[from_idx].poly_idx = int(poly_idx); - lines_colored_out[from_idx].local_line_idx = int(line_idx); - ++line_idx; - } - ++poly_idx; +static bool can_vertex_be_deleted(const VD::vertex_type &vertex) { + if (vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR || vertex.color() == VD_ANNOTATION::DELETED) + return false; + + return non_deleted_edge_count(vertex) <= 1; +} + +static void delete_vertex_deep(const VD::vertex_type &vertex) { + std::queue vertices_to_delete; + vertices_to_delete.emplace(&vertex); + + while (!vertices_to_delete.empty()) { + const VD::vertex_type &vertex_to_delete = *vertices_to_delete.front(); + vertices_to_delete.pop(); + vertex_to_delete.color(VD_ANNOTATION::DELETED); + + const VD::edge_type *edge = vertex_to_delete.incident_edge(); + do { + edge->color(VD_ANNOTATION::DELETED); + edge->twin()->color(VD_ANNOTATION::DELETED); + + if (edge->is_finite() && can_vertex_be_deleted(*edge->vertex1())) + vertices_to_delete.emplace(edge->vertex1()); + } while (edge = edge->prev()->twin(), edge != vertex_to_delete.incident_edge()); } } -// Voronoi edges produced by Voronoi generator cloud have coordinates that don't fit inside coord_t (int32_t). -// Because of that, this function tries to clip edges that have one endpoint of the edge inside the BoundingBox. -static inline Line clip_finite_voronoi_edge(const Voronoi::VD::edge_type &edge, const BoundingBoxf &bbox) -{ +static inline Vec2d mk_point_vec2d(const VD::vertex_type *point) { + assert(point != nullptr); + return {point->x(), point->y()}; +} + +static inline Vec2d mk_vector_vec2d(const VD::edge_type *edge) { + assert(edge != nullptr); + return mk_point_vec2d(edge->vertex1()) - mk_point_vec2d(edge->vertex0()); +} + +static inline Vec2d mk_flipped_vector_vec2d(const VD::edge_type *edge) { + assert(edge != nullptr); + return mk_point_vec2d(edge->vertex0()) - mk_point_vec2d(edge->vertex1()); +} + +static double edge_length(const VD::edge_type &edge) { assert(edge.is_finite()); - Vec2d v0 = mk_vec2(edge.vertex0()); - Vec2d v1 = mk_vec2(edge.vertex1()); - bool contains_v0 = bbox.contains(v0); - bool contains_v1 = bbox.contains(v1); - if ((contains_v0 && contains_v1) || (!contains_v0 && !contains_v1)) - return {mk_point(edge.vertex0()), mk_point(edge.vertex1())}; - - Vec2d vector = (v1 - v0).normalized() * bbox.size().norm(); - if (!contains_v0) - v0 = (v1 - vector); - else - v1 = (v0 + vector); - - return {v0.cast(), v1.cast()}; -} - -static MMU_Graph build_graph(size_t layer_idx, const std::vector> &color_poly) -{ - Voronoi::VD vd; - std::vector lines_colored = to_lines(color_poly); - const Polygons color_poly_tmp = colored_points_to_polygon(color_poly); - const Points points = to_points(color_poly_tmp); - const Lines lines = to_lines(color_poly_tmp); - - // The algorithm adds edges to the graph that are between two different colors. - // If a polygon is colored entirely with one color, we need to add at least one edge from that polygon artificially. - // Adding this edge is necessary for cases where the expolygon has an outer contour colored whole with one color - // and a hole colored with a different color. If an edge wasn't added to the graph, - // the entire expolygon would be colored with single random color instead of two different. - std::vector force_edge_adding(color_poly.size()); - - // For each polygon, check if it is all colored with the same color. If it is, we need to force adding one edge to it. - for (const std::vector &c_poly : color_poly) { - bool force_edge = true; - for (const ColoredLine &c_line : c_poly) - if (c_line.color != c_poly.front().color) { - force_edge = false; - break; - } - force_edge_adding[&c_poly - &color_poly.front()] = force_edge; - } - - vd.construct_voronoi(lines_colored.begin(), lines_colored.end()); - MMU_Graph graph; - graph.nodes.reserve(points.size() + vd.vertices().size()); - for (const Point &point : points) - graph.nodes.push_back({Vec2d(double(point.x()), double(point.y()))}); - - graph.add_contours(color_poly); - init_polygon_indices(graph, color_poly, lines_colored); - - assert(graph.nodes.size() == lines_colored.size()); - BoundingBox bbox = get_extents(color_poly_tmp); - graph.append_voronoi_vertices(vd, color_poly_tmp, bbox); - - auto get_prev_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram::const_edge_iterator &edge_it) -> ColoredLine { - size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx; - size_t contour_line_size = color_poly[lines_colored[edge_it->cell()->source_index()].poly_idx].size(); - size_t contour_prev_idx = graph.get_global_index(lines_colored[edge_it->cell()->source_index()].poly_idx, - (contour_line_local_idx > 0) ? contour_line_local_idx - 1 : contour_line_size - 1); - return lines_colored[contour_prev_idx]; - }; - - auto get_next_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram::const_edge_iterator &edge_it) -> ColoredLine { - size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx; - size_t contour_line_size = color_poly[lines_colored[edge_it->cell()->source_index()].poly_idx].size(); - size_t contour_next_idx = graph.get_global_index(lines_colored[edge_it->cell()->source_index()].poly_idx, - (contour_line_local_idx + 1) % contour_line_size); - return lines_colored[contour_next_idx]; - }; - - bbox.offset(scale_(10.)); - const BoundingBoxf bbox_clip(bbox.min.cast(), bbox.max.cast()); - const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); - - // Make a copy of the input segments with the double type. - std::vector segments; - for (const Line &line : lines) - segments.emplace_back(Voronoi::Internal::point_type(double(line.a(0)), double(line.a(1))), - Voronoi::Internal::point_type(double(line.b(0)), double(line.b(1)))); - - for (auto edge_it = vd.edges().begin(); edge_it != vd.edges().end(); ++edge_it) { - // Skip second half-edge - if (edge_it->cell()->source_index() > edge_it->twin()->cell()->source_index() || edge_it->color()) - continue; - - if (edge_it->is_infinite() && (edge_it->vertex0() != nullptr || edge_it->vertex1() != nullptr)) { - // Infinite edge is leading through a point on the counter, but there are no Voronoi vertices. - // So we could fix this case by computing the intersection between the contour line and infinity edge. - std::vector samples; - Voronoi::Internal::clip_infinite_edge(points, segments, *edge_it, bbox_dim_max, &samples); - if (samples.empty()) - continue; - - const Line edge_line(mk_point(samples[0]), mk_point(samples[1])); - const ColoredLine &contour_line = lines_colored[edge_it->cell()->source_index()]; - Point contour_intersection; - - if (line_intersection_with_epsilon(contour_line.line, edge_line, &contour_intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_it->cell()->source_index()); - const size_t from_idx = (edge_it->vertex1() != nullptr) ? edge_it->vertex1()->color() : edge_it->vertex0()->color(); - size_t to_idx = ((contour_line.line.a - contour_intersection).cast().squaredNorm() < - (contour_line.line.b - contour_intersection).cast().squaredNorm()) ? - graph_arc.from_idx : - graph_arc.to_idx; - if (from_idx != to_idx && from_idx < graph.nodes_count() && to_idx < graph.nodes_count()) { - graph.append_edge(from_idx, to_idx); - mark_processed(edge_it); - } - } - } else if (edge_it->is_finite()) { - // Both points are on contour, so skip them. In cases of duplicate Voronoi vertices, skip edges between the same two points. - if (graph.is_edge_connecting_two_contour_vertices(edge_it) || (edge_it->vertex0()->color() == edge_it->vertex1()->color())) - continue; - - const Line edge_line = clip_finite_voronoi_edge(*edge_it, bbox_clip); - const Line contour_line = lines_colored[edge_it->cell()->source_index()].line; - const ColoredLine colored_line = lines_colored[edge_it->cell()->source_index()]; - const ColoredLine contour_line_prev = get_prev_contour_line(edge_it); - const ColoredLine contour_line_next = get_next_contour_line(edge_it); - - if (edge_it->vertex0()->color() >= graph.nodes_count() || edge_it->vertex1()->color() >= graph.nodes_count()) { - enum class Vertex { VERTEX0, VERTEX1 }; - auto append_edge_if_intersects_with_contour = [&graph, &lines_colored, &edge_line, &contour_line](const voronoi_diagram::const_edge_iterator &edge_iterator, const Vertex vertex) { - Point intersection; - Line contour_line_twin = lines_colored[edge_iterator->twin()->cell()->source_index()].line; - if (line_intersection_with_epsilon(contour_line_twin, edge_line, &intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->twin()->cell()->source_index()); - const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line_twin, intersection) ? graph_arc.from_idx : - graph_arc.to_idx; - graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l); - } else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { - const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->cell()->source_index()); - const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line, intersection) ? graph_arc.from_idx : graph_arc.to_idx; - graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l); - } - mark_processed(edge_iterator); - }; - - if (edge_it->vertex0()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex0())) - append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX0); - - if (edge_it->vertex1()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex1())) - append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX1); - } else if (graph.is_edge_attach_to_contour(edge_it)) { - mark_processed(edge_it); - // Skip edges witch connection two points on a contour - if (graph.is_edge_connecting_two_contour_vertices(edge_it)) - continue; - - const size_t from_idx = edge_it->vertex0()->color(); - const size_t to_idx = edge_it->vertex1()->color(); - if (graph.is_vertex_on_contour(edge_it->vertex0())) { - if (is_point_closer_to_beginning_of_line(contour_line, edge_line.a)) { - if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.b)) { - graph.append_edge(from_idx, to_idx); - force_edge_adding[colored_line.poly_idx] = false; - } - } else { - if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.b)) { - graph.append_edge(from_idx, to_idx); - force_edge_adding[colored_line.poly_idx] = false; - } - } - } else { - assert(graph.is_vertex_on_contour(edge_it->vertex1())); - if (is_point_closer_to_beginning_of_line(contour_line, edge_line.b)) { - if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.a)) { - graph.append_edge(from_idx, to_idx); - force_edge_adding[colored_line.poly_idx] = false; - } - } else { - if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.a)) { - graph.append_edge(from_idx, to_idx); - force_edge_adding[colored_line.poly_idx] = false; - } - } - } - } else if (Point intersection; line_intersection_with_epsilon(contour_line, edge_line, &intersection)) { - mark_processed(edge_it); - Vec2d real_v0_double = graph.nodes[edge_it->vertex0()->color()].point; - Vec2d real_v1_double = graph.nodes[edge_it->vertex1()->color()].point; - Point real_v0 = Point(coord_t(real_v0_double.x()), coord_t(real_v0_double.y())); - Point real_v1 = Point(coord_t(real_v1_double.x()), coord_t(real_v1_double.y())); - - if (is_point_closer_to_beginning_of_line(contour_line, intersection)) { - Line first_part(intersection, real_v0); - Line second_part(intersection, real_v1); - - if (!has_same_color(contour_line_prev, colored_line)) { - if (points_inside(contour_line_prev.line, contour_line, first_part.b)) - graph.append_edge(edge_it->vertex0()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx); - - if (points_inside(contour_line_prev.line, contour_line, second_part.b)) - graph.append_edge(edge_it->vertex1()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx); - } - } else { - const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx; - const Vec2d int_point_double = graph.nodes[int_point_idx].point; - const Point int_point = Point(coord_t(int_point_double.x()), coord_t(int_point_double.y())); - - const Line first_part(int_point, real_v0); - const Line second_part(int_point, real_v1); - - if (!has_same_color(contour_line_next, colored_line)) { - if (points_inside(contour_line, contour_line_next.line, first_part.b)) - graph.append_edge(edge_it->vertex0()->color(), int_point_idx); - - if (points_inside(contour_line, contour_line_next.line, second_part.b)) - graph.append_edge(edge_it->vertex1()->color(), int_point_idx); - } - } - } - } - } - - for (auto edge_it = vd.edges().begin(); edge_it != vd.edges().end(); ++edge_it) { - // Skip second half-edge and processed edges - if (edge_it->cell()->source_index() > edge_it->twin()->cell()->source_index() || edge_it->color()) - continue; - - if (edge_it->is_finite() && !bool(edge_it->color()) && edge_it->vertex0()->color() < graph.nodes_count() && - edge_it->vertex1()->color() < graph.nodes_count()) { - // Skip cases, when the edge is between two same vertices, which is in cases two near vertices were merged together. - if (edge_it->vertex0()->color() == edge_it->vertex1()->color()) - continue; - - size_t from_idx = edge_it->vertex0()->color(); - size_t to_idx = edge_it->vertex1()->color(); - graph.append_edge(from_idx, to_idx); - } - mark_processed(edge_it); - } - - graph.remove_nodes_with_one_arc(); - return graph; -} - -static inline Polygon to_polygon(const std::vector &lines) -{ - Polygon poly_out; - poly_out.points.reserve(lines.size()); - for (const Linef &line : lines) - poly_out.points.emplace_back(mk_point(line.a)); - return poly_out; -} - -// Returns list of polygons and assigned colors. -// It iterates through all nodes on the border between two different colors, and from this point, -// start selection always left most edges for every node to construct CCW polygons. -// Assumes that graph is planar (without self-intersection edges) -static std::vector extract_colored_segments(const MMU_Graph &graph, const size_t num_extruders) -{ - std::vector used_arcs(graph.arcs.size(), false); - // When there is no next arc, then is returned original_arc or edge with is marked as used - auto get_next = [&graph, &used_arcs](const Linef &process_line, const MMU_Graph::Arc &original_arc) -> const MMU_Graph::Arc & { - std::vector> sorted_arcs; - for (const size_t &arc_idx : graph.nodes[original_arc.to_idx].arc_idxs) { - const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; - if (graph.nodes[arc.to_idx].point == process_line.a || used_arcs[arc_idx]) - continue; - - assert(original_arc.to_idx == arc.from_idx); - Vec2d process_line_vec_n = (process_line.a - process_line.b).normalized(); - Vec2d neighbour_line_vec_n = (graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point).normalized(); - - double angle = ::acos(std::clamp(neighbour_line_vec_n.dot(process_line_vec_n), -1.0, 1.0)); - if (Slic3r::cross2(neighbour_line_vec_n, process_line_vec_n) < 0.0) - angle = 2.0 * (double) PI - angle; - - sorted_arcs.emplace_back(&arc, angle); - } - - std::sort(sorted_arcs.begin(), sorted_arcs.end(), - [](std::pair &l, std::pair &r) -> bool { return l.second < r.second; }); - - // Try to return left most edge witch is unused - for (auto &sorted_arc : sorted_arcs) - if (size_t arc_idx = sorted_arc.first - &graph.arcs.front(); !used_arcs[arc_idx]) - return *sorted_arc.first; - - if (sorted_arcs.empty()) - return original_arc; - - return *(sorted_arcs.front().first); - }; - - auto all_arc_used = [&used_arcs](const MMU_Graph::Node &node) -> bool { - return std::all_of(node.arc_idxs.cbegin(), node.arc_idxs.cend(), [&used_arcs](const size_t &arc_idx) -> bool { return used_arcs[arc_idx]; }); - }; - - std::vector expolygons_segments(num_extruders + 1); - for (size_t node_idx = 0; node_idx < graph.all_border_points; ++node_idx) { - const MMU_Graph::Node &node = graph.nodes[node_idx]; - - for (const size_t &arc_idx : node.arc_idxs) { - const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; - if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || used_arcs[arc_idx]) - continue; - - Linef process_line(node.point, graph.nodes[arc.to_idx].point); - used_arcs[arc_idx] = true; - - std::vector face_lines; - face_lines.emplace_back(process_line); - Vec2d start_p = process_line.a; - - Linef p_vec = process_line; - const MMU_Graph::Arc *p_arc = &arc; - do { - const MMU_Graph::Arc &next = get_next(p_vec, *p_arc); - size_t next_arc_idx = &next - &graph.arcs.front(); - face_lines.emplace_back(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point); - if (used_arcs[next_arc_idx]) - break; - - used_arcs[next_arc_idx] = true; - p_vec = Linef(graph.nodes[next.from_idx].point, graph.nodes[next.to_idx].point); - p_arc = &next; - } while (graph.nodes[p_arc->to_idx].point != start_p || !all_arc_used(graph.nodes[p_arc->to_idx])); - - if (Polygon poly = to_polygon(face_lines); poly.is_counter_clockwise() && poly.is_valid()) - expolygons_segments[arc.color].emplace_back(std::move(poly)); - } - } - return expolygons_segments; + return mk_vector_vec2d(&edge).norm(); } // Used in remove_multiple_edges_in_vertices() // Returns length of edge with is connected to contour. To this length is include other edges with follows it if they are almost straight (with the // tolerance of 15) And also if node between two subsequent edges is connected only to these two edges. -static inline double compute_edge_length(const MMU_Graph &graph, const size_t start_idx, const size_t &start_arc_idx) +static inline double calc_total_edge_length(const VD::edge_type &starting_edge) { - assert(start_arc_idx < graph.arcs.size()); - std::vector used_arcs(graph.arcs.size(), false); - - used_arcs[start_arc_idx] = true; - const MMU_Graph::Arc *arc = &graph.arcs[start_arc_idx]; - size_t idx = start_idx; - double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm(); - while (graph.nodes[arc->to_idx].arc_idxs.size() == 2) { - bool found = false; - for (const size_t &arc_idx : graph.nodes[arc->to_idx].arc_idxs) { - if (const MMU_Graph::Arc &arc_n = graph.arcs[arc_idx]; arc_n.type == MMU_Graph::ARC_TYPE::NON_BORDER && !used_arcs[arc_idx] && arc_n.to_idx != idx) { - Linef first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point); - Linef second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point); - - Vec2d first_line_vec = (first_line.a - first_line.b); - Vec2d second_line_vec = (second_line.b - second_line.a); - Vec2d first_line_vec_n = first_line_vec.normalized(); - Vec2d second_line_vec_n = second_line_vec.normalized(); - double angle = ::acos(std::clamp(first_line_vec_n.dot(second_line_vec_n), -1.0, 1.0)); - if (Slic3r::cross2(first_line_vec_n, second_line_vec_n) < 0.0) - angle = 2.0 * (double) PI - angle; - - if (std::abs(angle - PI) >= (PI / 12)) - continue; - - idx = arc->to_idx; - arc = &arc_n; - - line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm(); - used_arcs[arc_idx] = true; - found = true; - break; - } - } - if (!found) + double total_edge_length = edge_length(starting_edge); + const VD::edge_type *prev = &starting_edge; + do { + if (prev->is_finite() && non_deleted_edge_count(*prev->vertex1()) > 2) break; - } - return line_total_length; + bool found_next_edge = false; + const VD::edge_type *current = prev->next(); + do { + if (current->color() == VD_ANNOTATION::DELETED) + continue; + + Vec2d first_line_vec_n = mk_flipped_vector_vec2d(prev).normalized(); + Vec2d second_line_vec_n = mk_vector_vec2d(current).normalized(); + double angle = ::acos(std::clamp(first_line_vec_n.dot(second_line_vec_n), -1.0, 1.0)); + if (Slic3r::cross2(first_line_vec_n, second_line_vec_n) < 0.0) + angle = 2.0 * (double) PI - angle; + + if (std::abs(angle - PI) >= (PI / 12)) + continue; + + prev = current; + found_next_edge = true; + total_edge_length += edge_length(*current); + + break; + } while (current = current->prev()->twin(), current != prev->next()); + + if (!found_next_edge) + break; + + } while (prev != &starting_edge); + + return total_edge_length; } -// Used for fixing double Voronoi edges for concave parts of the polygon. -static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vector> &color_poly) +// When a Voronoi vertex has more than one Voronoi edge (for example, in concave parts of a polygon), +// we leave just one Voronoi edge in the Voronoi vertex. +// This Voronoi edge is selected based on a heuristic. +static void remove_multiple_edges_in_vertex(const VD::vertex_type &vertex) { + if (non_deleted_edge_count(vertex) <= 1) + return; + + std::vector> edges_to_check; + const VD::edge_type *edge = vertex.incident_edge(); + do { + if (edge->color() == VD_ANNOTATION::DELETED) + continue; + + edges_to_check.emplace_back(edge, calc_total_edge_length(*edge)); + } while (edge = edge->prev()->twin(), edge != vertex.incident_edge()); + + std::sort(edges_to_check.begin(), edges_to_check.end(), [](const auto &l, const auto &r) -> bool { + return l.second > r.second; + }); + + while (edges_to_check.size() > 1) { + const VD::edge_type &edge_to_check = *edges_to_check.back().first; + edge_to_check.color(VD_ANNOTATION::DELETED); + edge_to_check.twin()->color(VD_ANNOTATION::DELETED); + + if (const VD::vertex_type &vertex_to_delete = *edge_to_check.vertex1(); can_vertex_be_deleted(vertex_to_delete)) + delete_vertex_deep(vertex_to_delete); + + edges_to_check.pop_back(); + } +} + +// Returns list of ExPolygons for each extruder + 1 for default unpainted regions. +// It iterates through all nodes on the border between two different colors, and from this point, +// start selection always left most edges for every node to construct CCW polygons. +static std::vector extract_colored_segments(const std::vector &colored_polygons, + const size_t num_extruders, + const size_t layer_idx) { - std::vector>> colored_segments = get_all_segments(color_poly); - for (const std::vector> &colored_segment_p : colored_segments) { - size_t poly_idx = &colored_segment_p - &colored_segments.front(); - for (const std::pair &colored_segment : colored_segment_p) { - size_t first_idx = graph.get_global_index(poly_idx, colored_segment.first); - size_t second_idx = graph.get_global_index(poly_idx, (colored_segment.second + 1) % graph.polygon_sizes[poly_idx]); - Linef seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point); + const ColoredLines colored_lines = to_lines(colored_polygons); + const BoundingBox bbox = get_extents(colored_polygons); - if (graph.nodes[first_idx].arc_idxs.size() >= 3) { - std::vector> arc_to_check; - for (const size_t &arc_idx : graph.nodes[first_idx].arc_idxs) { - MMU_Graph::Arc &n_arc = graph.arcs[arc_idx]; - if (n_arc.type == MMU_Graph::ARC_TYPE::NON_BORDER) { - double total_len = compute_edge_length(graph, first_idx, arc_idx); - arc_to_check.emplace_back(&n_arc, total_len); - } - } - std::sort(arc_to_check.begin(), arc_to_check.end(), - [](std::pair &l, std::pair &r) -> bool { return l.second > r.second; }); + auto get_next_contour_line = [&colored_polygons](const ColoredLine &line) -> const ColoredLine & { + size_t contour_line_size = colored_polygons[line.poly_idx].size(); + size_t contour_next_idx = (line.local_line_idx + 1) % contour_line_size; + return colored_polygons[line.poly_idx][contour_next_idx]; + }; - while (arc_to_check.size() > 1) { - graph.remove_edge(first_idx, arc_to_check.back().first->to_idx); - arc_to_check.pop_back(); - } - } + Voronoi::VD vd; + vd.construct_voronoi(colored_lines.begin(), colored_lines.end()); + + // First, mark each Voronoi vertex on the input polygon to prevent it from being deleted later. + for (const Voronoi::VD::cell_type &cell : vd.cells()) { + if (cell.is_degenerate() || !cell.contains_segment()) + continue; + + if (const Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid()) + cell_range.edge_begin->vertex0()->color(VD_ANNOTATION::VERTEX_ON_CONTOUR); + } + + // Second, remove all Voronoi vertices that are outside the bounding box of input polygons. + // Such Voronoi vertices are definitely not inside of input polygons, so we don't care about them. + for (const Voronoi::VD::vertex_type &vertex : vd.vertices()) { + if (vertex.color() == VD_ANNOTATION::DELETED || vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR) + continue; + + if (!Geometry::VoronoiUtils::is_in_range(vertex) || !bbox.contains(Geometry::VoronoiUtils::to_point(vertex).cast())) + delete_vertex_deep(vertex); + } + + // Third, remove all Voronoi edges that are infinite. + for (const Voronoi::VD::edge_type &edge : vd.edges()) { + if (edge.color() != VD_ANNOTATION::DELETED && edge.is_infinite()) { + edge.color(VD_ANNOTATION::DELETED); + edge.twin()->color(VD_ANNOTATION::DELETED); + + if (edge.vertex0() != nullptr && can_vertex_be_deleted(*edge.vertex0())) + delete_vertex_deep(*edge.vertex0()); + + if (edge.vertex1() != nullptr && can_vertex_be_deleted(*edge.vertex1())) + delete_vertex_deep(*edge.vertex1()); } } + + // Fourth, remove all edges that point outward from the input polygon. + for (Voronoi::VD::cell_type cell : vd.cells()) { + if (cell.is_degenerate() || !cell.contains_segment()) + continue; + + if (const Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid()) { + const ColoredLine ¤t_line = Geometry::VoronoiUtils::get_source_segment(cell, colored_lines.begin(), colored_lines.end()); + const ColoredLine &next_line = get_next_contour_line(current_line); + + const VD::edge_type *edge = cell_range.edge_begin; + do { + if (edge->color() == VD_ANNOTATION::DELETED) + continue; + + if (!points_inside(current_line.line, next_line.line, Geometry::VoronoiUtils::to_point(edge->vertex1()).cast())) { + edge->color(VD_ANNOTATION::DELETED); + edge->twin()->color(VD_ANNOTATION::DELETED); + delete_vertex_deep(*edge->vertex1()); + } + } while (edge = edge->prev()->twin(), edge != cell_range.edge_begin); + } + } + + // Fifth, if a Voronoi vertex has more than one Voronoi edge, remove all but one of them based on heuristics. + for (const Voronoi::VD::vertex_type &vertex : vd.vertices()) { + if (vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR) + remove_multiple_edges_in_vertex(vertex); + } + +#ifdef MM_SEGMENTATION_DEBUG_GRAPH + { + static int iRun = 0; + export_graph_to_svg(debug_out_path("mm-graph-%d-%d.svg", layer_idx, iRun++), vd, colored_polygons); + } +#endif // MM_SEGMENTATION_DEBUG_GRAPH + + // Sixth, extract the colored segments from the annotated Voronoi diagram. + std::vector segmented_expolygons_per_extruder(num_extruders + 1); + for (const Voronoi::VD::cell_type &cell : vd.cells()) { + if (cell.is_degenerate() || !cell.contains_segment()) + continue; + + if (const Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid()) { + if (cell_range.edge_begin->vertex0()->color() != VD_ANNOTATION::VERTEX_ON_CONTOUR) + continue; + + const ColoredLine source_segment = Geometry::VoronoiUtils::get_source_segment(cell, colored_lines.begin(), colored_lines.end()); + + Polygon segmented_polygon; + segmented_polygon.points.emplace_back(source_segment.line.b); + + // We have ensured that each segmented_polygon have to start at edge_begin->vertex0() and end at edge_end->vertex1(). + const VD::edge_type *edge = cell_range.edge_begin; + do { + if (edge->color() == VD_ANNOTATION::DELETED) + continue; + + const VD::vertex_type &next_vertex = *edge->vertex1(); + segmented_polygon.points.emplace_back(Geometry::VoronoiUtils::to_point(next_vertex).cast()); + edge->color(VD_ANNOTATION::DELETED); + + if (next_vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR || next_vertex.color() == VD_ANNOTATION::DELETED) { + assert(next_vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR); + break; + } + + edge = edge->twin(); + } while (edge = edge->twin()->next(), edge != cell_range.edge_begin); + + if (edge->vertex1() != cell_range.edge_end->vertex1()) + continue; + + cell_range.edge_begin->vertex0()->color(VD_ANNOTATION::DELETED); + segmented_expolygons_per_extruder[source_segment.color].emplace_back(std::move(segmented_polygon)); + } + } + + // Merge all polygons together for each extruder + for (auto &segmented_expolygons : segmented_expolygons_per_extruder) + segmented_expolygons = union_ex(segmented_expolygons); + + return segmented_expolygons_per_extruder; } static void cut_segmented_layers(const std::vector &input_expolygons, @@ -1255,7 +832,7 @@ static void cut_segmented_layers(const std::vector &input_exp const float interlocking_depth, const std::function &throw_on_cancel_callback) { - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - begin"; const float interlocking_cut_width = interlocking_depth > 0.f ? std::max(cut_width - interlocking_depth, 0.f) : 0.f; tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &interlocking_cut_width, &throw_on_cancel_callback](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { @@ -1271,7 +848,7 @@ static void cut_segmented_layers(const std::vector &input_exp } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - end"; } static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d &trafo) @@ -1282,12 +859,10 @@ static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d return false; } -//#define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM - -// Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo -static inline std::vector> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object, - const std::vector &input_expolygons, - const std::function &throw_on_cancel_callback) +// Returns MM segmentation of top and bottom layers based on painting in MM segmentation gizmo +static inline std::vector> mm_segmentation_top_and_bottom_layers(const PrintObject &print_object, + const std::vector &input_expolygons, + const std::function &throw_on_cancel_callback) { const size_t num_extruders = print_object.print()->config().nozzle_diameter.size() + 1; const size_t num_layers = input_expolygons.size(); @@ -1310,22 +885,22 @@ static inline std::vector> mmu_segmentation_top_and_bott std::vector zs = zs_from_layers(layers); Transform3d object_trafo = print_object.trafo_centered(); -#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM +#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM static int iRun = 0; -#endif // NDEBUG +#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM if (max_top_layers > 0 || max_bottom_layers > 0) { for (const ModelVolume *mv : print_object.model_object()->volumes) if (mv->is_model_part()) { const Transform3d volume_trafo = object_trafo * mv->get_matrix(); for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) { - const indexed_triangle_set painted = mv->mmu_segmentation_facets.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx)); -#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM + const indexed_triangle_set painted = mv->mm_segmentation_facets.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx)); +#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM { static int iRun = 0; its_write_obj(painted, debug_out_path("mm-painted-patch-%d-%d.obj", iRun ++, extruder_idx).c_str()); } -#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM +#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM if (! painted.indices.empty()) { std::vector top, bottom; if (!zs.empty() && is_volume_sinking(painted, volume_trafo)) { @@ -1380,7 +955,7 @@ static inline std::vector> mmu_segmentation_top_and_bott filter_out_small_polygons(top_raw, Slic3r::sqr(scale_(0.1f))); filter_out_small_polygons(bottom_raw, Slic3r::sqr(scale_(0.1f))); -#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM +#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM { const char* colors[] = { "aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow" }; static int iRun = 0; @@ -1402,7 +977,7 @@ static inline std::vector> mmu_segmentation_top_and_bott } ++ iRun; } -#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM +#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM std::vector> triangles_by_color_bottom(num_extruders); std::vector> triangles_by_color_top(num_extruders); @@ -1533,7 +1108,7 @@ static std::vector> merge_segmented_layers( segmented_regions_merged.assign(num_layers, std::vector(num_extruders)); assert(num_extruders + 1 == top_and_bottom_layers.size()); - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging segmented layers in parallel - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - begin"; tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { assert(segmented_regions[layer_idx].size() == num_extruders + 1); @@ -1560,12 +1135,12 @@ static std::vector> merge_segmented_layers( } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging segmented layers in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - end"; return segmented_regions_merged; } -#ifdef MMU_SEGMENTATION_DEBUG_REGIONS +#ifdef MM_SEGMENTATION_DEBUG_REGIONS static void export_regions_to_svg(const std::string &path, const std::vector ®ions, const ExPolygons &lslices) { const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"}; @@ -1577,35 +1152,15 @@ static void export_regions_to_svg(const std::string &path, const std::vector= 0 && extrude_idx < int(colors.size())) - svg.draw(by_extruder, colors[extrude_idx], stroke_width); + if (extrude_idx < int(colors.size())) + svg.draw(by_extruder, colors[extrude_idx]); else - svg.draw(by_extruder, "black", stroke_width); + svg.draw(by_extruder, "black"); } } -#endif // MMU_SEGMENTATION_DEBUG_REGIONS +#endif // MM_SEGMENTATION_DEBUG_REGIONS -#ifdef MMU_SEGMENTATION_DEBUG_GRAPH -static void export_graph_to_svg(const std::string &path, const MMU_Graph &graph, const ExPolygons &lslices) -{ - const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"}; - coordf_t stroke_width = scale_(0.05); - BoundingBox bbox = get_extents(lslices); - bbox.offset(scale_(1.)); - ::Slic3r::SVG svg(path.c_str(), bbox); - for (const MMU_Graph::Node &node : graph.nodes) - for (const size_t &arc_idx : node.arc_idxs) { - const MMU_Graph::Arc &arc = graph.arcs[arc_idx]; - Line arc_line(mk_point(node.point), mk_point(graph.nodes[arc.to_idx].point)); - if (arc.type == MMU_Graph::ARC_TYPE::BORDER && arc.color >= 0 && arc.color < int(colors.size())) - svg.draw(arc_line, colors[arc.color], stroke_width); - else - svg.draw(arc_line, "black", stroke_width); - } -} -#endif // MMU_SEGMENTATION_DEBUG_GRAPH - -#ifdef MMU_SEGMENTATION_DEBUG_INPUT +#ifdef MM_SEGMENTATION_DEBUG_INPUT void export_processed_input_expolygons_to_svg(const std::string &path, const LayerRegionPtrs ®ions, const ExPolygons &processed_input_expolygons) { coordf_t stroke_width = scale_(0.05); @@ -1615,13 +1170,14 @@ void export_processed_input_expolygons_to_svg(const std::string &path, const Lay ::Slic3r::SVG svg(path.c_str(), bbox); for (LayerRegion *region : regions) - svg.draw_outline(region->slices.surfaces, "blue", "cyan", stroke_width); + for (const Surface &surface : region->slices()) + svg.draw_outline(surface, "blue", "cyan", stroke_width); svg.draw_outline(processed_input_expolygons, "red", "pink", stroke_width); } -#endif // MMU_SEGMENTATION_DEBUG_INPUT +#endif // MM_SEGMENTATION_DEBUG_INPUT -#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES +#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES static void export_painted_lines_to_svg(const std::string &path, const std::vector> &all_painted_lines, const ExPolygons &lslices) { const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"}; @@ -1637,10 +1193,10 @@ static void export_painted_lines_to_svg(const std::string &path, const std::vect for (const PaintedLine &painted_line : painted_lines) svg.draw(painted_line.projected_line, painted_line.color < int(colors.size()) ? colors[painted_line.color] : "black", stroke_width); } -#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES +#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES -#ifdef MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS -static void export_colorized_polygons_to_svg(const std::string &path, const std::vector> &colorized_polygons, const ExPolygons &lslices) +#ifdef MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS +static void export_colorized_polygons_to_svg(const std::string &path, const std::vector &colorized_polygons, const ExPolygons &lslices) { const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"}; coordf_t stroke_width = scale_(0.05); @@ -1648,19 +1204,19 @@ static void export_colorized_polygons_to_svg(const std::string &path, const std: bbox.offset(scale_(1.)); ::Slic3r::SVG svg(path.c_str(), bbox); - for (const std::vector &colorized_polygon : colorized_polygons) + for (const ColoredLines &colorized_polygon : colorized_polygons) for (const ColoredLine &colorized_line : colorized_polygon) svg.draw(colorized_line.line, colorized_line.color < int(colors.size())? colors[colorized_line.color] : "black", stroke_width); } -#endif // MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS +#endif // MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS // Check if all ColoredLine representing a single layer uses the same color. -static bool has_layer_only_one_color(const std::vector> &colored_polygons) +static bool has_layer_only_one_color(const std::vector &colored_polygons) { assert(!colored_polygons.empty()); assert(!colored_polygons.front().empty()); int first_line_color = colored_polygons.front().front().color; - for (const std::vector &colored_polygon : colored_polygons) + for (const ColoredLines &colored_polygon : colored_polygons) for (const ColoredLine &colored_line : colored_polygon) if (first_line_color != colored_line.color) return false; @@ -1682,8 +1238,12 @@ std::vector> multi_material_segmentation_by_painting(con throw_on_cancel_callback(); +#ifdef MM_SEGMENTATION_DEBUG + static int iRun = 0; +#endif // MM_SEGMENTATION_DEBUG + // Merge all regions and remove small holes - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - begin"; tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); @@ -1705,15 +1265,12 @@ std::vector> multi_material_segmentation_by_painting(con // Calling expolygons_simplify fixed these issues. input_expolygons[layer_idx] = remove_duplicates(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), scaled(0.01), PI/6); -#ifdef MMU_SEGMENTATION_DEBUG_INPUT - { - static int iRun = 0; - export_processed_input_expolygons_to_svg(debug_out_path("mm-input-%d-%d.svg", layer_idx, iRun++), layers[layer_idx]->regions(), input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_INPUT +#ifdef MM_SEGMENTATION_DEBUG_INPUT + export_processed_input_expolygons_to_svg(debug_out_path("mm-input-%d-%d.svg", layer_idx, iRun), layers[layer_idx]->regions(), input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_INPUT } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - end"; std::vector layer_bboxes(num_layers); for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { @@ -1736,12 +1293,12 @@ std::vector> multi_material_segmentation_by_painting(con edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.))); } - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - begin"; for (const ModelVolume *mv : print_object.model_object()->volumes) { tbb::parallel_for(tbb::blocked_range(1, num_extruders + 1), [&mv, &print_object, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) { throw_on_cancel_callback(); - const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx)); + const indexed_triangle_set custom_facets = mv->mm_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx)); if (!mv->is_model_part() || custom_facets.indices.empty()) continue; @@ -1817,39 +1374,30 @@ std::vector> multi_material_segmentation_by_painting(con } }); // end of parallel_for } - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - end"; - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - painted layers count: " + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - painted layers count: " << std::count_if(painted_lines.begin(), painted_lines.end(), [](const std::vector &pl) { return !pl.empty(); }); - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - begin"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - begin"; tbb::parallel_for(tbb::blocked_range(0, num_layers), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); if (!painted_lines[layer_idx].empty()) { -#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES - { - static int iRun = 0; - export_painted_lines_to_svg(debug_out_path("mm-painted-lines-%d-%d.svg", layer_idx, iRun++), {painted_lines[layer_idx]}, input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES +#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES + export_painted_lines_to_svg(debug_out_path("mm-painted-lines-%d-%d.svg", layer_idx, iRun), {painted_lines[layer_idx]}, input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES std::vector> post_processed_painted_lines = post_process_painted_lines(edge_grids[layer_idx].contours(), std::move(painted_lines[layer_idx])); -#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES - { - static int iRun = 0; - export_painted_lines_to_svg(debug_out_path("mm-painted-lines-post-processed-%d-%d.svg", layer_idx, iRun++), post_processed_painted_lines, input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES +#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES + export_painted_lines_to_svg(debug_out_path("mm-painted-lines-post-processed-%d-%d.svg", layer_idx, iRun), post_processed_painted_lines, input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES - std::vector> color_poly = colorize_contours(edge_grids[layer_idx].contours(), post_processed_painted_lines); + std::vector color_poly = colorize_contours(edge_grids[layer_idx].contours(), post_processed_painted_lines); -#ifdef MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS - { - static int iRun = 0; - export_colorized_polygons_to_svg(debug_out_path("mm-colorized_polygons-%d-%d.svg", layer_idx, iRun++), color_poly, input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS +#ifdef MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS + export_colorized_polygons_to_svg(debug_out_path("mm-colorized_polygons-%d-%d.svg", layer_idx, iRun), color_poly, input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS assert(!color_poly.empty()); assert(!color_poly.front().empty()); @@ -1857,30 +1405,16 @@ std::vector> multi_material_segmentation_by_painting(con // If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer. segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx]; } else { - MMU_Graph graph = build_graph(layer_idx, color_poly); - remove_multiple_edges_in_vertices(graph, color_poly); - graph.remove_nodes_with_one_arc(); - -#ifdef MMU_SEGMENTATION_DEBUG_GRAPH - { - static int iRun = 0; - export_graph_to_svg(debug_out_path("mm-graph-final-%d-%d.svg", layer_idx, iRun++), graph, input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_GRAPH - - segmented_regions[layer_idx] = extract_colored_segments(graph, num_extruders); + segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx); } -#ifdef MMU_SEGMENTATION_DEBUG_REGIONS - { - static int iRun = 0; - export_regions_to_svg(debug_out_path("mm-regions-sides-%d-%d.svg", layer_idx, iRun++), segmented_regions[layer_idx], input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_REGIONS +#ifdef MM_SEGMENTATION_DEBUG_REGIONS + export_regions_to_svg(debug_out_path("mm-regions-sides-%d-%d.svg", layer_idx, iRun), segmented_regions[layer_idx], input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_REGIONS } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - end"; throw_on_cancel_callback(); if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f) { @@ -1889,19 +1423,20 @@ std::vector> multi_material_segmentation_by_painting(con } // The first index is extruder number (includes default extruder), and the second one is layer number - std::vector> top_and_bottom_layers = mmu_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback); + std::vector> top_and_bottom_layers = mm_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback); throw_on_cancel_callback(); std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_extruders, throw_on_cancel_callback); throw_on_cancel_callback(); -#ifdef MMU_SEGMENTATION_DEBUG_REGIONS - { - static int iRun = 0; - for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) - export_regions_to_svg(debug_out_path("mm-regions-merged-%d-%d.svg", layer_idx, iRun++), segmented_regions_merged[layer_idx], input_expolygons[layer_idx]); - } -#endif // MMU_SEGMENTATION_DEBUG_REGIONS +#ifdef MM_SEGMENTATION_DEBUG_REGIONS + for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) + export_regions_to_svg(debug_out_path("mm-regions-merged-%d-%d.svg", layer_idx, iRun), segmented_regions_merged[layer_idx], input_expolygons[layer_idx]); +#endif // MM_SEGMENTATION_DEBUG_REGIONS + +#ifdef MM_SEGMENTATION_DEBUG + ++iRun; +#endif // MM_SEGMENTATION_DEBUG return segmented_regions_merged; } diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 5f100ab4bd..1ac2c72304 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -78,8 +78,8 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, mv_dst.supported_facets.assign(mv_src.supported_facets); assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id()); mv_dst.seam_facets.assign(mv_src.seam_facets); - assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id()); - mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets); + assert(mv_dst.mm_segmentation_facets.id() == mv_src.mm_segmentation_facets.id()); + mv_dst.mm_segmentation_facets.assign(mv_src.mm_segmentation_facets); //FIXME what to do with the materials? // mv_dst.m_material_id = mv_src.m_material_id; ++ i_src; @@ -1374,7 +1374,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ std::vector painting_extruders; if (const auto &volumes = print_object.model_object()->volumes; num_extruders > 1 && - std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) { + std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mm_segmentation_facets.empty(); }) != volumes.end()) { //FIXME be more specific! Don't enumerate extruders that are not used for painting! painting_extruders.assign(num_extruders, 0); std::iota(painting_extruders.begin(), painting_extruders.end(), 1); diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 3da6e3aa3d..d285cab41b 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -728,7 +728,7 @@ void PrintObject::slice_volumes() // Is any ModelVolume MMU painted? if (const auto& volumes = this->model_object()->volumes; m_print->config().nozzle_diameter.size() > 1 && - std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) { + std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mm_segmentation_facets.empty(); }) != volumes.end()) { // If XY Size compensation is also enabled, notify the user that XY Size compensation // would not be used because the object is multi-material painted. diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bba3c602fa..bcaa4a0cd2 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4922,7 +4922,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const for (GLVolume *vol : visible_volumes) { const int obj_idx = vol->object_idx(); const int vol_idx = vol->volume_idx(); - const bool render_as_painted = is_enabled_painted_thumbnail && obj_idx >= 0 && vol_idx >= 0 && !model_objects[obj_idx]->volumes[vol_idx]->mmu_segmentation_facets.empty(); + const bool render_as_painted = is_enabled_painted_thumbnail && obj_idx >= 0 && vol_idx >= 0 && !model_objects[obj_idx]->volumes[vol_idx]->mm_segmentation_facets.empty(); GLShaderProgram* shader = wxGetApp().get_shader(render_as_painted ? "mm_gouraud" : "gouraud_light"); if (shader == nullptr) continue; @@ -4958,7 +4958,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const const ModelVolume& model_volume = *model_objects[obj_idx]->volumes[vol_idx]; const size_t extruder_idx = get_extruder_color_idx(model_volume, extruders_count); TriangleSelectorMmGui ts(model_volume.mesh(), extruders_colors, extruders_colors[extruder_idx]); - ts.deserialize(model_volume.mmu_segmentation_facets.get_data(), true); + ts.deserialize(model_volume.mm_segmentation_facets.get_data(), true); ts.request_update_render_data(); ts.render(nullptr, model_matrix); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index db8e372e92..313787682d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1905,7 +1905,7 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type) cnv->get_gizmos_manager().reset_all_states(); Plater::TakeSnapshot(plater, _L("Remove Multi Material painting")); for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes) - mv->mmu_segmentation_facets.reset(); + mv->mm_segmentation_facets.reset(); break; case InfoItemType::Sinking: @@ -2897,7 +2897,7 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio [type](const ModelVolume *mv) { return !(type == InfoItemType::CustomSupports ? mv->supported_facets.empty() : type == InfoItemType::CustomSeam ? mv->seam_facets.empty() : - mv->mmu_segmentation_facets.empty()); + mv->mm_segmentation_facets.empty()); }); break; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 4fa9140d0d..b2d62445a1 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -648,7 +648,7 @@ void Preview::update_layers_slider_mode() if ((volume->config.has("extruder") && volume->config.option("extruder")->getInt() != 0 && // extruder isn't default volume->config.option("extruder")->getInt() != extruder) || - !volume->mmu_segmentation_facets.empty()) + !volume->mm_segmentation_facets.empty()) return false; for (const auto& range : object->layer_config_ranges) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 7f4d4c28d6..b6c72c0b44 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -517,7 +517,7 @@ void GLGizmoMmuSegmentation::update_model_object() const if (! mv->is_model_part()) continue; ++idx; - updated |= mv->mmu_segmentation_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx].get()); } if (updated) { @@ -547,7 +547,7 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors() size_t extruder_idx = get_extruder_color_idx(*mv, extruders_count); m_triangle_selectors.emplace_back(std::make_unique(*mesh, m_modified_extruders_colors, m_original_extruders_colors[extruder_idx])); // Reset of TriangleSelector is done inside TriangleSelectorMmGUI's constructor, so we don't need it to perform it again in deserialize(). - m_triangle_selectors.back()->deserialize(mv->mmu_segmentation_facets.get_data(), false); + m_triangle_selectors.back()->deserialize(mv->mm_segmentation_facets.get_data(), false); m_triangle_selectors.back()->request_update_render_data(); } m_original_volumes_extruder_idxs = get_extruder_id_for_volumes(*mo); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index dcb5a52491..2d00674c08 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3702,7 +3702,7 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const // We need to make sure that the painted data point to existing triangles. new_volume->supported_facets.assign(old_volume->supported_facets); new_volume->seam_facets.assign(old_volume->seam_facets); - new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets); + new_volume->mm_segmentation_facets.assign(old_volume->mm_segmentation_facets); } std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); @@ -7918,10 +7918,10 @@ void Plater::clear_before_change_mesh(int obj_idx, const std::string ¬ificati // may be different and they would make no sense. bool paint_removed = false; for (ModelVolume* mv : mo->volumes) { - paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty(); + paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mm_segmentation_facets.empty(); mv->supported_facets.reset(); mv->seam_facets.reset(); - mv->mmu_segmentation_facets.reset(); + mv->mm_segmentation_facets.reset(); } if (paint_removed) { // snapshot_time is captured by copy so the lambda knows where to undo/redo to.