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