diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 34caee311..cd58f34c6 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -573,6 +573,59 @@ 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) +{ + for (VoronoiUtils::vd_t::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 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; + + Point start_source_point, end_source_point; + VoronoiUtils::vd_t::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()) + 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 Geometry::VoronoiDiagram &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); !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; +} + void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) { #ifdef ARACHNE_DEBUG @@ -614,36 +667,35 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) } #endif - // Try to detect cases when some Voronoi vertex is missing and when - // the Voronoi diagram is not planar. - // When any Voronoi vertex is missing, or the Voronoi diagram is not - // planar, rotate the input polygon and try again. - const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments); - // Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514, #8446 and #8846. - const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments); - const double fix_angle = PI / 6; + // 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); + const double fix_angle = PI / 6; std::unordered_map vertex_mapping; // polys_copy is referenced through items stored in the std::vector segments. Polygons polys_copy = polys; - if (has_missing_voronoi_vertex || !is_voronoi_diagram_planar) { - if (has_missing_voronoi_vertex) + if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) { + if (status == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX) BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth."; - else if (!is_voronoi_diagram_planar) + else if (status == VoronoiDiagramStatus::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) + BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth."; vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle); assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments)); assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments)); + assert(!detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments)); if (detect_missing_voronoi_vertex(voronoi_diagram, segments)) BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input."; else if (!Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments)) BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input."; + else if (detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments)) + BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input."; } - bool degenerated_voronoi_diagram = has_missing_voronoi_vertex || !is_voronoi_diagram_planar; - 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()) { @@ -652,35 +704,35 @@ process_voronoi_diagram: Point start_source_point; Point end_source_point; - vd_t::edge_type* starting_vonoroi_edge = nullptr; - vd_t::edge_type* ending_vonoroi_edge = nullptr; + vd_t::edge_type* starting_voronoi_edge = nullptr; + vd_t::edge_type* ending_voronoi_edge = nullptr; // Compute and store result in above variables - + if (cell.contains_point()) { - const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments); + const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments); if (!keep_going) continue; } else { assert(cell.contains_segment()); - computeSegmentCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments); + computeSegmentCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments); } - - if (!starting_vonoroi_edge || !ending_vonoroi_edge) { + + if (!starting_voronoi_edge || !ending_voronoi_edge) { assert(false && "Each cell should start / end in a polygon vertex"); continue; } - + // Copy start to end edge to graph edge_t* prev_edge = nullptr; - assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() >= std::numeric_limits::lowest()); - transferEdge(start_source_point, VoronoiUtils::p(starting_vonoroi_edge->vertex1()).cast(), *starting_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments); - node_t* starting_node = vd_node_to_he_node[starting_vonoroi_edge->vertex0()]; + 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()]; 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_t::edge_type* vd_edge = starting_vonoroi_edge->next(); vd_edge != ending_vonoroi_edge; vd_edge = vd_edge->next()) { + for (vd_t::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()); @@ -692,12 +744,12 @@ process_voronoi_diagram: Point v2 = VoronoiUtils::p(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_vonoroi_edge); + graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge); } - assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() >= std::numeric_limits::lowest()); - transferEdge(VoronoiUtils::p(ending_vonoroi_edge->vertex0()).cast(), end_source_point, *ending_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments); + 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); prev_edge->to->data.distance_to_boundary = 0; } @@ -705,9 +757,9 @@ process_voronoi_diagram: // 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 (!degenerated_voronoi_diagram && has_missing_twin_edge(this->graph)) { + 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."; - degenerated_voronoi_diagram = true; + status = VoronoiDiagramStatus::OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION; vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle); assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments)); @@ -724,14 +776,14 @@ process_voronoi_diagram: goto process_voronoi_diagram; } - if (degenerated_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 (degenerated_voronoi_diagram) + if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fix_angle, vertex_mapping); #ifdef ARACHNE_DEBUG @@ -742,7 +794,7 @@ process_voronoi_diagram: graph.collapseSmallEdges(); - // Set [incident_edge] the the first possible edge that way we can iterate over all reachable edges from node.incident_edge, + // Set [incident_edge] the first possible edge that way we can iterate over all reachable edges from node.incident_edge, // without needing to iterate backward for (edge_t& edge : graph.edges) if (!edge.prev) diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index 819b71367..b4029d586 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -9,6 +9,7 @@ #include // smart pointers #include #include // pair +#include #include "utils/HalfEdgeGraph.hpp" #include "utils/PolygonsSegmentIndex.hpp" @@ -229,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. */ - 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_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); /*! * Compute the range of line segments that surround a cell of the skeletal @@ -255,7 +256,7 @@ protected: * /return Whether the cell is inside of the polygon. If it's outside of the * polygon we should skip processing it altogether. */ - 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_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); /*! * 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 @@ -597,6 +598,8 @@ protected: * Genrate small segments for local maxima where the beading would only result in a single bead */ void generateLocalMaximaSingleBeads(); + + friend bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector &segments); }; } // namespace Slic3r::Arachne