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