diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dda23ccdd..43096caa11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,7 +134,7 @@ set(engine_SRCS # Except main.cpp. src/utils/ToolpathVisualizer.cpp src/utils/VoronoiUtils.cpp src/utils/VoxelUtils.cpp - ) + include/utils/BoundedAreaHierarchy.h) add_library(_CuraEngine STATIC ${engine_SRCS} ${engine_PB_SRCS}) use_threads(_CuraEngine) diff --git a/include/SkeletalTrapezoidation.h b/include/SkeletalTrapezoidation.h index 2b920465c4..3460cc881a 100644 --- a/include/SkeletalTrapezoidation.h +++ b/include/SkeletalTrapezoidation.h @@ -159,6 +159,12 @@ class SkeletalTrapezoidation */ void constructFromPolygons(const Polygons& polys); + bool validate() const; + + + void filterCells(vd_t& voronoi_diagram, std::vector& points, std::vector& segments); + + /*! * 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 diff --git a/include/SkeletalTrapezoidationGraph.h b/include/SkeletalTrapezoidationGraph.h index 4a3f9bc594..fba3881ba5 100644 --- a/include/SkeletalTrapezoidationGraph.h +++ b/include/SkeletalTrapezoidationGraph.h @@ -9,6 +9,7 @@ #include "SkeletalTrapezoidationEdge.h" #include "SkeletalTrapezoidationJoint.h" #include "utils/HalfEdgeGraph.h" +#include "utils/PolygonsSegmentIndex.h" namespace cura { @@ -71,6 +72,8 @@ class SkeletalTrapezoidationGraph: public HalfEdgeGraph segments; + /*! * If an edge is too small, collapse it and its twin and fix the surrounding edges to ensure a consistent graph. * @@ -83,7 +86,7 @@ class SkeletalTrapezoidationGraph: public HalfEdgeGraph + +#ifndef CURAENGINE_BOUNDEDAREAHIERARCHY_H +#define CURAENGINE_BOUNDEDAREAHIERARCHY_H + +namespace cura +{ +template +class BoundedAreaHierarchy +{ + struct Edge + { + const Point start; + const Point end; + + Edge(const Point start, const Point end) : start(start), end(end) + { + } + + AABB getAABB() const + { + AABB aabb; + aabb.include(start); + aabb.include(end); + return aabb; + } + }; + + struct Node + { + AABB aabb; + // if child_a is 0, then this is a leaf node, child_b + // is the index of the edge in the edges vector + // this works as the first element of the bounded_area_hierarchy-tree + // is the root; no child points to the root in a tree data structure + int child_a; + int child_b; + }; + + std::vector> edges; + std::vector bounded_area_hierarchy; + + /*! + * \brief calculate_bounds + * \param start index of the first edge to include + * \param end index of the last edge to include (inclusive) + * \return the bounding box of the edges + */ + AABB calculate_bounds(const int start, const int end) + { + AABB aabb; + for (int i = start; i <= end; i++) + { + aabb.include(edges[i].first.getAABB()); + } + return aabb; + } + + // Quicksort algorithm based on https://www.geeksforgeeks.org/cpp-program-for-quicksort/ + void quick_sort_primitives(const int axis, const int first, const int last) { + if (first < last) { + float pivot = get_axis(axis, vertices[indices[(first + last) / 2] * 3]); + + int i = first - 1; + + for (int j = first; j <= last - 1; j ++) { + if (get_axis(axis, centroids[indices[j]]) <= pivot) { + i++; + Swap(&indices[i], &indices[j]); + } + } + Swap(&indices[i + 1], &indices[last]); + + int pivotIndex = i + 1; + + QuickSortPrimitives(axis, first, pivotIndex - 1); + QuickSortPrimitives(axis, pivotIndex + 1, last); + } + } + + void construct_search_structure(const int start, const int end) + { + if (end - start == 1) + { + bounded_area_hierarchy[start].child_a = 0; + bounded_area_hierarchy[start].child_b = start; + return; + } + + edges.sort(start, end, [](const std::pair& a, const std::pair& b) { + return a.first.getAABB().getCenter().x < b.first.getAABB().getCenter().x; + }); + + + const int middle = (start + end) / 2; + construct_search_structure(start, middle); + construct_search_structure(middle, end); + bounded_area_hierarchy[start].child_a = middle; + bounded_area_hierarchy[start].child_b = 0; + bounded_area_hierarchy[start].aabb = bounded_area_hierarchy[start].aabb.combine(bounded_area_hierarchy[middle].aabb); + } + +public: + BoundedAreaHierarchy(); + + /*! + * \brief insert_edge inserts an edge into the bounded area hierarchy + * \param start the start point of the edge + * \param end the end point of the edge + * \param data the data associated with the edge + */ + void insert_edge(const Point start, const Point end, const edge_data data) + { + edges.emplace_back(Edge(start, end), data); + } + + void draw_debug_svg(const std::string file_name) + { + if (! bounded_area_hierarchy.empty()) return; + + AABB outer_aabb = bounded_area_hierarchy[0].aabb; + outer_aabb.expand(1000); + SVG svg(file_name, outer_aabb); + + std::vector stack; + stack.push_back(0); + while (!stack.empty()) + { + const int node_index = stack.back(); + stack.pop_back(); + const Node& node = bounded_area_hierarchy[node_index]; + const AABB aabb = node.aabb; + const Polygon polygons = aabb.toPolygon(); + + svg.writePolygon(polygons); + + if (node.child_a == 0) + { + const int edge_index = node.child_b; + const Edge& edge = edges[edge_index].first; + svg.writeLine(edge.start, edge.end); + } + else + { + stack.push_back(node.child_a); + stack.push_back(node.child_b); + } + } + } + + void construct_search_structure() + { + bounded_area_hierarchy.clear(); + bounded_area_hierarchy.reserve(edges.size() * 2 - 1); + for (const auto& edge : edges) + { + bounded_area_hierarchy.emplace_back(Node{ edge.first.getAABB(), 0, 0 }); + } + construct_search_structure(0, edges.size()); + } + + void radius_search(const Point point, const coord_t radius, std::vector result) const + { + std::vector stack; + stack.push_back(0); + while (! stack.empty()) + { + const int node_index = stack.back(); + stack.pop_back(); + const Node& node = bounded_area_hierarchy[node_index]; + if (node.child_a == 0) + { + const int edge_index = node.child_b; + const Edge& edge = edges[edge_index].first; + if (edge.start.distance_to(point) < radius || edge.end.distance_to(point) < radius) + { + result.push_back(edges[edge_index].second); + } + } + else + { + const Node& child_a = bounded_area_hierarchy[node.child_a]; + const Node& child_b = bounded_area_hierarchy[node.child_b]; + if (child_a.aabb.distance_to(point) < radius) + { + stack.push_back(node.child_a); + } + if (child_b.aabb.distance_to(point) < radius) + { + stack.push_back(node.child_b); + } + } + } + } + + std::vector radius_search(const Point point, const coord_t radius) const + { + std::vector result; + radius_search(point, radius, result); + return result; + } +}; +} // namespace cura + +#endif // CURAENGINE_BOUNDEDAREAHIERARCHY_H diff --git a/include/utils/polygon.h b/include/utils/polygon.h index 5c71e713cf..66574920e9 100644 --- a/include/utils/polygon.h +++ b/include/utils/polygon.h @@ -547,6 +547,8 @@ class PolygonRef : public ConstPolygonRef void removeCollinearPoints(const AngleRadians max_deviation_angle); + void removeSmallEdges(const unsigned int max_dist); + /*! * Removes consecutive line segments with same orientation and changes this polygon. * @@ -1158,6 +1160,20 @@ class Polygons } } } + + void removeSmallEdges(const unsigned int max_dist = 10) + { + Polygons& thiss = *this; + for (size_t p = 0; p < size(); p++) + { + thiss[p].removeSmallEdges(max_dist); + if (thiss[p].size() < 3) + { + remove(p); + p--; + } + } + } public: void scale(const Ratio& ratio) diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index 580c5781e3..e4b6dfd45e 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -41,6 +41,8 @@ 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& points, const std::vector& segments) { + graph.segments = 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 @@ -88,8 +90,7 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& assert(twin->prev->twin); // Back rib assert(twin->prev->twin->prev); // Prev segment along parabola - constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped - graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end); + graph.makeRib(prev_edge, start_source_point, end_source_point); } assert(prev_edge); } @@ -141,8 +142,7 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& if (p1_idx < discretized.size() - 1) { // Rib for last segment gets introduced outside this function! - constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped - graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end); + graph.makeRib(prev_edge, start_source_point, end_source_point); } } assert(prev_edge); @@ -150,6 +150,79 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& } } +void SkeletalTrapezoidation::filterCells(vd_t& vonoroi_diagram, std::vector& points, std::vector& segments) +{ + for (vd_t::cell_type cell : vonoroi_diagram.cells()) + { + if (! cell.incident_edge()) + { // There is no spoon + spdlog::info("there is no spoon"); + continue; + } + Point start_source_point; + Point end_source_point; + vd_t::edge_type* starting_vonoroi_edge = nullptr; + vd_t::edge_type* ending_vonoroi_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, points, segments); + if (! keep_going) + { + continue; + } + } + else if (cell.contains_segment()) + { + computeSegmentCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, points, segments); + } + else + { + assert(false && "Cell should contain point or segment"); + } + + if (! starting_vonoroi_edge || ! ending_vonoroi_edge) + { + spdlog::info("No starting or ending edge found"); + // assert(false && "Each cell should start / end in a polygon vertex"); + continue; + } + + // Copy start to end edge to graph + edge_t* prev_edge = nullptr; + transferEdge(start_source_point, VoronoiUtils::p(starting_vonoroi_edge->vertex1()), *starting_vonoroi_edge, prev_edge, start_source_point, end_source_point, points, segments); + node_t* starting_node = vd_node_to_he_node[starting_vonoroi_edge->vertex0()]; + starting_node->data.distance_to_boundary = 0; + + graph.makeRib(prev_edge, start_source_point, end_source_point); + for (vd_t::edge_type* vd_edge = starting_vonoroi_edge->next(); vd_edge != ending_vonoroi_edge; vd_edge = vd_edge->next()) + { + // assert(vd_edge->is_finite()); + auto v1 = VoronoiUtils::p(vd_edge->vertex0()); + auto v2 = VoronoiUtils::p(vd_edge->vertex1()); + transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, points, segments); + + graph.makeRib(prev_edge, start_source_point, end_source_point); + } + + transferEdge(VoronoiUtils::p(ending_vonoroi_edge->vertex0()), end_source_point, *ending_vonoroi_edge, prev_edge, start_source_point, end_source_point, points, segments); + prev_edge->to->data.distance_to_boundary = 0; + } +} + +bool SkeletalTrapezoidation::validate() const +{ + for (auto& edge : graph.edges) + { + if (!edge.twin) { + return false; + } + } + return true; +} + + std::vector SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const std::vector& points, const std::vector& segments) { /*Terminology in this function assumes that the edge moves horizontally from @@ -264,7 +337,7 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, { if (cell.incident_edge()->is_infinite()) { - return false; // Infinite edges only occur outside of the polygon. Don't copy any part of this cell. + return false; // Infinite edges only occur outside the polygon. Don't copy any part of this cell. } // Check if any point of the cell is inside or outside polygon // Copy whole cell into graph or not at all @@ -287,6 +360,11 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, vd_t::edge_type* vd_edge = cell.incident_edge(); do { + if (!vd_edge->is_finite()) + { + spdlog::warn("assert failure: vd_edge->is_finite()"); + graph.drawGraph(); + } assert(vd_edge->is_finite()); Point p1 = VoronoiUtils::p(vd_edge->vertex1()); if (p1 == source_point) @@ -298,11 +376,22 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, } else { - assert((VoronoiUtils::p(vd_edge->vertex0()) == source_point || ! 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."); + if (!(VoronoiUtils::p(vd_edge->vertex0()) == source_point || ! vd_edge->is_secondary())) { + spdlog::warn("assert failure: vd_edge->vertex0()) == source_point || ! vd_edge->is_secondary())"); + } +// assert((VoronoiUtils::p(vd_edge->vertex0()) == source_point || ! vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input."); } } while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge()); - assert(starting_vd_edge && ending_vd_edge); - assert(starting_vd_edge != ending_vd_edge); + + if (!(starting_vd_edge && ending_vd_edge)) { + spdlog::warn("assert failure: starting_vd_edge && ending_vd_edge"); + } + if (!(starting_vd_edge != ending_vd_edge)) { + spdlog::warn("assert failure: starting_vd_edge != ending_vd_edge"); + } + +// assert(starting_vd_edge && ending_vd_edge); +// assert(starting_vd_edge != ending_vd_edge); return true; } @@ -332,7 +421,7 @@ void SkeletalTrapezoidation::computeSegmentCellRange(vd_t::cell_type& cell, } Point v0 = VoronoiUtils::p(edge->vertex0()); Point v1 = VoronoiUtils::p(edge->vertex1()); - assert(! (v0 == to && v1 == from)); +// assert(! (v0 == to && v1 == from)); if (v0 == to && ! after_start) // Use the last edge which starts in source_segment.to { starting_vd_edge = edge; @@ -350,8 +439,8 @@ void SkeletalTrapezoidation::computeSegmentCellRange(vd_t::cell_type& cell, } } while (edge = edge->next(), edge != cell.incident_edge()); - assert(starting_vd_edge && ending_vd_edge); - assert(starting_vd_edge != ending_vd_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(); @@ -385,73 +474,72 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) std::vector points; // Remains empty - std::vector segments; + graph.segments.clear(); for (size_t poly_idx = 0; poly_idx < polys.size(); poly_idx++) { ConstPolygonRef poly = polys[poly_idx]; for (size_t point_idx = 0; point_idx < poly.size(); point_idx++) { - segments.emplace_back(&polys, poly_idx, point_idx); + graph.segments.emplace_back(&polys, poly_idx, point_idx); } } vd_t vonoroi_diagram; - construct_voronoi(segments.begin(), segments.end(), &vonoroi_diagram); + construct_voronoi(graph.segments.begin(), graph.segments.end(), &vonoroi_diagram); - for (vd_t::cell_type cell : vonoroi_diagram.cells()) + bool valid = true; + for (auto& edge : vonoroi_diagram.edges()) { - if (! cell.incident_edge()) - { // There is no spoon - continue; - } - Point start_source_point; - Point end_source_point; - vd_t::edge_type* starting_vonoroi_edge = nullptr; - vd_t::edge_type* ending_vonoroi_edge = nullptr; - // Compute and store result in above variables - - if (cell.contains_point()) + if (!edge.twin()) { - const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, points, segments); - if (! keep_going) - { - continue; - } - } - else - { - computeSegmentCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, points, segments); + valid = false; } + } - if (! starting_vonoroi_edge || ! ending_vonoroi_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; - transferEdge(start_source_point, VoronoiUtils::p(starting_vonoroi_edge->vertex1()), *starting_vonoroi_edge, prev_edge, start_source_point, end_source_point, points, segments); - node_t* starting_node = vd_node_to_he_node[starting_vonoroi_edge->vertex0()]; - starting_node->data.distance_to_boundary = 0; + spdlog::info("A validate(): {}; graph.edges.size(): {}", valid, graph.edges.size()); - 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()) - { - assert(vd_edge->is_finite()); - Point v1 = VoronoiUtils::p(vd_edge->vertex0()); - Point v2 = VoronoiUtils::p(vd_edge->vertex1()); - transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, points, segments); + filterCells(vonoroi_diagram, points, graph.segments); - graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_vonoroi_edge); - } + spdlog::info("B validate(): {}; graph.edges.size(): {}", validate(), graph.edges.size()); - transferEdge(VoronoiUtils::p(ending_vonoroi_edge->vertex0()), end_source_point, *ending_vonoroi_edge, prev_edge, start_source_point, end_source_point, points, segments); - prev_edge->to->data.distance_to_boundary = 0; + if (!validate()) { + spdlog::warn("assert failure: validate()"); + graph.drawGraph(); } +// { +// AABB aabb; +// for (auto& segment : segments) +// { +// aabb.include(segment.from()); +// aabb.include(segment.to()); +// } +// aabb.expand(1000); +// +// SVG svg("filename3.svg", aabb); +// +// spdlog::info("segments.size() = {}", segments.size()); +// for (auto& segment : segments) +// { +// svg.writeLine(segment.from(), segment.to(), SVG::Color::BLACK); +// } +// +// for (auto& edge : vonoroi_diagram.cells()) { +// +// } +// } +// } + + separatePointyQuadEndNodes(); + spdlog::info("C validate(): {}", validate()); + + if (!validate()) { + spdlog::warn("assert failure: C validate()"); + graph.drawGraph(); + } + graph.collapseSmallEdges(); @@ -486,6 +574,60 @@ void SkeletalTrapezoidation::separatePointyQuadEndNodes() node_t* new_node = &graph.nodes.back(); new_node->incident_edge = quad_start; quad_start->from = new_node; + + if (!quad_start->twin) { + + +// { +// AABB aabb; +// for (edge_t& edge : graph.edges) +// { +// aabb.include(edge.from->p); +// aabb.include(edge.to->p); +// } +// aabb.expand(1000); +// +// SVG svg("filename.svg", aabb); +// +// +// for (const auto& edge : graph.edges) +// { +// auto missing_prop = ! edge.twin || ! edge.prev || ! edge.next; +// svg.writeLine(edge.from->p, edge.to->p, missing_prop ? SVG::Color::RED : SVG::Color::GREEN, 0.01); +// } +// } +// +// { +// vd_t vonoroi_diagram; +// construct_voronoi(segments.begin(), segments.end(), &vonoroi_diagram); +// +// AABB aabb; +// for (auto& segment : segments) +// { +// aabb.include(segment.from()); +// aabb.include(segment.to()); +// } +// aabb.expand(1000); +// +// SVG svg("filename2.svg", aabb); +// +// spdlog::info("segments.size() = {}", segments.size()); +// for (auto& segment : segments) +// { +// spdlog::info("segment.from() = ({}, {}), segment.to() = ({}, {})", segment.from().X, segment.from().Y, segment.to().X, segment.to().Y); +// svg.writeLine(segment.from(), segment.to(), SVG::Color::BLACK); +// } +// +// for (auto& edge : vonoroi_diagram.edges()) { +// +// if (edge.is_finite()) { +// auto missing_prop = ! edge.twin() || ! edge.prev() || ! edge.next(); +// svg.writeLine(VoronoiUtils::p(edge.vertex0()), VoronoiUtils::p(edge.vertex1()), missing_prop ? SVG::Color::RED : SVG::Color::GREEN, 0.1); +// } +// } +// } + } + quad_start->twin->to = new_node; } } @@ -837,7 +979,7 @@ void SkeletalTrapezoidation::generateTransitionMids(ptr_vector_t= transitions->back().pos); +// assert((! edge.data.hasTransitions(ignore_empty)) || mid_pos >= transitions->back().pos); if (! edge.data.hasTransitions(ignore_empty)) { edge_transitions.emplace_back(std::make_shared>()); @@ -1052,11 +1194,11 @@ void SkeletalTrapezoidation::generateAllTransitionEnds(ptr_vector_tdata.distance_to_boundary <= edge.to->data.distance_to_boundary); +// assert(edge.from->data.distance_to_boundary <= edge.to->data.distance_to_boundary); for (TransitionMiddle& transition_middle : transition_positions) { - assert(transition_positions.front().pos <= transition_middle.pos); - assert(transition_middle.pos <= transition_positions.back().pos); +// assert(transition_positions.front().pos <= transition_middle.pos); +// assert(transition_middle.pos <= transition_positions.back().pos); generateTransitionEnds(edge, transition_middle.pos, transition_middle.lower_bead_count, edge_transition_ends); } } @@ -1509,6 +1651,11 @@ void SkeletalTrapezoidation::generateSegments() SkeletalTrapezoidation::edge_t* SkeletalTrapezoidation::getQuadMaxRedgeTo(edge_t* quad_start_edge) { +// if (! quad_start_edge->prev) +// { +// spdlog::warn("Quad start edge has no prev"); +// graph.drawGraph(); +// } assert(quad_start_edge->prev == nullptr); assert(quad_start_edge->from->data.distance_to_boundary == 0); coord_t max_R = -1; @@ -1956,6 +2103,12 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ } if (edge_from_peak->next) { + if (! edge_from_peak->next->twin) + { + spdlog::error("Edge from peak has no data!"); + graph.drawGraph(); + } + graph.drawGraph(); LineJunctions to_next_junctions = *edge_from_peak->next->twin->data.getExtrusionJunctions(); while (! to_junctions.empty() && ! to_next_junctions.empty() && to_junctions.back().perimeter_index <= to_next_junctions.front().perimeter_index) { @@ -1969,7 +2122,7 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ spdlog::warn("The edge we're about to connect is already connected!"); } } - assert(std::abs(int(from_junctions.size()) - int(to_junctions.size())) <= 1); // at transitions one end has more beads +// assert(std::abs(int(from_junctions.size()) - int(to_junctions.size())) <= 1); // at transitions one end has more beads if (std::abs(int(from_junctions.size()) - int(to_junctions.size())) > 1) { spdlog::warn("Can't create a transition when connecting two perimeters where the number of beads differs too much! {} vs. {}", from_junctions.size(), to_junctions.size()); @@ -1980,7 +2133,7 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ { ExtrusionJunction& from = from_junctions[from_junctions.size() - 1 - junction_rev_idx]; ExtrusionJunction& to = to_junctions[to_junctions.size() - 1 - junction_rev_idx]; - assert(from.perimeter_index == to.perimeter_index); +// assert(from.perimeter_index == to.perimeter_index); if (from.perimeter_index != to.perimeter_index) { spdlog::warn("Connecting two perimeters with different indices! Perimeter {} and {}", from.perimeter_index, to.perimeter_index); diff --git a/src/SkeletalTrapezoidationGraph.cpp b/src/SkeletalTrapezoidationGraph.cpp index ed8cc1035d..e8c1522d1c 100644 --- a/src/SkeletalTrapezoidationGraph.cpp +++ b/src/SkeletalTrapezoidationGraph.cpp @@ -282,6 +282,12 @@ void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist) nodes.erase(node_locator[quad_mid->to]); + if (!quad_mid->prev || !quad_mid->prev->next || !quad_mid->next || !quad_mid->next->prev || !quad_mid->twin || !quad_mid->twin->prev || !quad_mid->twin->next || !quad_mid->twin->next->prev || !quad_mid->twin->prev->next) + { + spdlog::warn("Missing edge in quad collapse."); + drawGraph(); + } + quad_mid->prev->next = quad_mid->next; quad_mid->next->prev = quad_mid->prev; quad_mid->twin->next->prev = quad_mid->twin->prev; @@ -328,12 +334,49 @@ void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist) } } -void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end) +void SkeletalTrapezoidationGraph::drawGraph() const +{ + AABB aabb; + for (auto& edge : edges) + { + aabb.include(edge.from->p); + aabb.include(edge.to->p); + } + aabb.expand(1000); + + SVG svg("filename.svg", aabb, 0.1); + + spdlog::info("svg scale: {}", svg.getScale()); + + for (auto& segment : segments) + { + svg.writeLine(segment.from(), segment.to(), SVG::Color::BLACK, 0.2); + } + + for (auto& edge : edges) + { +// spdlog::info("edge.from->p ({}, {}), edge.to->p ({}, {})", edge.from->p.X, edge.from->p.Y, edge.to->p.X, edge.to->p.Y); + if (!edge.twin) { + spdlog::info("WRONG edge.from->p ({}, {}), edge.to->p ({}, {})", edge.from->p.X, edge.from->p.Y, edge.to->p.X, edge.to->p.Y); + svg.writeLine(edge.from->p, edge.to->p, SVG::Color::RED, 0.2); + svg.writePoint(edge.from->p, false, 100.0, SVG::Color::BLUE); + svg.writePoint(edge.to->p, false, 100.0, SVG::Color::BLUE); + } + } + + for (const auto& edge : edges) + { + auto missing_prop = ! edge.twin || ! edge.prev || ! edge.next; + svg.writeLine(edge.from->p, edge.to->p, missing_prop ? SVG::Color::RED : SVG::Color::GREEN, 0.2); + } +} + +void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point) { Point p = LinearAlg2D::getClosestOnLine(prev_edge->to->p, start_source_point, end_source_point); coord_t dist = vSize(prev_edge->to->p - p); prev_edge->to->data.distance_to_boundary = dist; - assert(dist >= 0); +// assert(dist >= 0); nodes.emplace_front(SkeletalTrapezoidationJoint(), p); node_t* node = &nodes.front(); diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 534f235bf7..74b4cf502e 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "WallToolPaths.h" @@ -13,6 +14,8 @@ #include "utils/polygonUtils.h" #include "ExtruderTrain.h" #include "utils/PolylineStitcher.h" +#include "utils/BoundedAreaHierarchy.h" + #include "utils/Simplify.h" namespace cura @@ -56,27 +59,47 @@ WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0 const std::vector& WallToolPaths::generate() { + spdlog::info("WallToolPaths::generate()"); // Sometimes small slivers of polygons mess up the prepared_outline. By performing an open-close operation // with half the minimum printable feature size or minimum line width, these slivers are removed, while still // keeping enough information to not degrade the print quality; // These features can't be printed anyhow. See PR CuraEngine#1811 for some screenshots const coord_t close_distance = settings.get("fill_outline_gaps") ? settings.get("min_feature_size") / 2 - 3 : settings.get("min_wall_line_width") / 2 - 3; - const coord_t open_distance = 300; + const coord_t open_distance = close_distance; const AngleRadians transitioning_angle = settings.get("wall_transition_angle"); constexpr coord_t discretization_step_size = MM2INT(0.8); + { + AABB aabb; + aabb.include(outline); + aabb.expand(1000); + SVG svg("output.svg", aabb); + svg.writePolygons(outline); + } + // Simplify outline for boost::voronoi consumption. Absolutely no // - self intersections, // - near-self intersections or // - collinear points // allowed: - Polygons prepared_outline = outline - .offset(open_distance) - .offset(-open_distance - close_distance, ClipperLib::jtRound) - .offset(close_distance, ClipperLib::jtRound); - prepared_outline = Simplify(settings).polygon(prepared_outline); - prepared_outline.removeSmallAreas(small_area_length * small_area_length, false); - prepared_outline.removeCollinearPoints(AngleRadians(0.005)); + cura::Polygons prepared_outline = outline.offset(open_distance); + +// cura::Polygons prepared_outline = outline +// .offset(-close_distance) +// .offset(open_distance + close_distance) +// .offset(-open_distance); +// prepared_outline.removeSmallAreas(small_area_length * small_area_length, false); +// prepared_outline.removeSmallEdges(10); +// prepared_outline.removeCollinearPoints(cura::AngleRadians(0.01)); + + { + AABB aabb; + aabb.include(prepared_outline); + aabb.expand(1000); + SVG svg("output2.svg", aabb); + svg.writePolygons(prepared_outline); + } + if (prepared_outline.area() <= 0) { @@ -311,7 +334,7 @@ void WallToolPaths::separateOutInnerContour() { for (const ExtrusionJunction& j : line) { - assert(j.w == 0); +// assert(j.w == 0); } } #endif // DEBUG diff --git a/src/utils/AABB.cpp b/src/utils/AABB.cpp index 1d6fab5e0f..b181e8d371 100644 --- a/src/utils/AABB.cpp +++ b/src/utils/AABB.cpp @@ -147,6 +147,59 @@ void AABB::expand(int dist) max.Y += dist; } +coord_t AABB::distanceSquared(const cura::Point& p) const +{ + const Point lt = min; + const Point rt = Point(max.X, min.Y); + const Point rb = max; + const Point lb = Point(min.X, max.Y); + + if (contains(p)) + { + return 0; + } + else if (p.X < lt.X && p.Y < lt.Y) + { + return vSize2(p - lt); + } + else if (p.X > rt.X && p.Y < rt.Y) + { + return vSize2(p - rt); + } + else if (p.X > rb.X && p.Y > rb.Y) + { + return vSize2(p - rb); + } + else if (p.X < lb.X && p.Y > lb.Y) + { + return vSize2(p - lb); + } + else if (p.X < min.X) + { + const auto dist = min.X - p.X; + return dist * dist; + } + else if (p.X > max.X) + { + const auto dist = p.X - max.X; + return dist * dist; + } + else if (p.Y < min.Y) + { + const auto dist = min.Y - p.Y; + return dist * dist; + } + else if (p.Y > max.Y) + { + const auto dist = p.Y - max.Y; + return dist * dist; + } + else + { + assert(false && "Not a possible state"); + } +} + Polygon AABB::toPolygon() const { Polygon ret; diff --git a/src/utils/VoronoiUtils.cpp b/src/utils/VoronoiUtils.cpp index b260a74c3a..4b7f01799b 100644 --- a/src/utils/VoronoiUtils.cpp +++ b/src/utils/VoronoiUtils.cpp @@ -167,7 +167,7 @@ std::vector VoronoiUtils::discretizeParabola(const Point& p, const Segmen // are more than 10 microns away from the projected apex bool add_apex = (sx - px) * dir < -10 && (ex - px) * dir > 10; - assert(! (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) { RUN_ONCE(spdlog::warn("Failing to discretize parabola! Must add an apex or one of the endpoints.")); diff --git a/src/utils/polygon.cpp b/src/utils/polygon.cpp index 1e1c2f0a5a..a5692caa1c 100644 --- a/src/utils/polygon.cpp +++ b/src/utils/polygon.cpp @@ -427,6 +427,58 @@ Polygons ConstPolygonRef::offset(int distance, ClipperLib::JoinType join_type, d return ret; } +void PolygonRef::removeSmallEdges(const unsigned int min_dist) +{ + auto is_small = [min_dist](const Point& p1, const Point& p2) { + return vSize2(p1 - p2) <= min_dist * min_dist; + }; + + int first_large_edge = 0; + while (is_small((*path).back(), (*path)[first_large_edge])) + { + first_large_edge++; + } + + ClipperLib::Path new_path; + new_path.push_back((*path)[first_large_edge]); + + for (int i = first_large_edge + 1; i < path->size(); i++) { + auto new_point = (*path)[i]; + if (is_small(new_path.back(), new_point)) + { + /* + * c---------d i-----------d + * small -> / | + * b | + * | becomes | + * | | + * | | + * a a + */ + + auto a = new_path.size() >= 2 ? new_path[new_path.size() - 2] : path->back(); + auto b = new_path.back(); + auto c = new_point; + auto d = (*path)[(i + 1) % path->size()]; + + Point intersection; + const bool did_intersect = LinearAlg2D::lineLineIntersection(a, b, c, d, intersection); + + if (did_intersect) + { +// new_path.back() = intersection; + } + } + else + { + new_path.push_back(new_point); + } + } + + *path = new_path; +} + + void PolygonRef::removeCollinearPoints(const AngleRadians max_deviation_angle) { auto is_collinear = [max_deviation_angle](const Point& p1, const Point& p2, const Point& p3) { @@ -450,56 +502,33 @@ void PolygonRef::removeCollinearPoints(const AngleRadians max_deviation_angle) return; } - ClipperLib::Path new_path; - - for (int i = 0; i < path->size(); i++) { - auto next = (*path)[i]; - while (new_path.size() >= 2) { - auto prev = new_path[new_path.size() - 2]; - auto pt = new_path[new_path.size() - 1]; - - if (is_collinear(prev, pt, next)) - { - new_path.pop_back(); - } - else - { - break; - } - } - new_path.push_back((*path)[i]); + // find the first non-collinear point + int first_non_collinear = 0; + while (first_non_collinear + 2 < path->size() && is_collinear((*path)[path->size() - 1], (*path)[first_non_collinear], (*path)[first_non_collinear + 1])) + { + first_non_collinear ++; } - while (new_path.size() >= 3) - { - auto prev = new_path[new_path.size() - 1]; - auto pt = new_path[0]; - auto next = new_path[1]; + ClipperLib::Path new_path; - if (is_collinear(prev, pt, next)) - { - new_path.erase(new_path.begin()); - } - else - { - break; + // add the first two points, we know that they are not collinear + new_path.push_back((*path)[path->size() - 1]); + new_path.push_back((*path)[first_non_collinear]); + + // add all non-collinear points, skip the last point, because it is already added + for (int i = first_non_collinear + 1; i < path->size() - 1; i++) { + auto new_point = (*path)[i]; + // while the new point is collinear with the last two points, remove the last point + while (new_path.size() >= 3 && is_collinear(new_path[new_path.size() - 2], new_path[new_path.size() - 1], new_point)) { + new_path.pop_back(); } + new_path.push_back(new_point); } - while (new_path.size() >= 3) + // remove collinear points at the end + while (new_path.size() >= 3 && is_collinear(new_path[new_path.size() - 2], new_path[new_path.size() - 1], new_path[0])) { - auto prev = new_path[new_path.size() - 2]; - auto pt = new_path[new_path.size() - 1]; - auto next = new_path[0]; - - if (is_collinear(prev, pt, next)) - { - new_path.pop_back(); - } - else - { - break; - } + new_path.pop_back(); } *path = new_path; diff --git a/tests/VoronoiCrashTest.cpp b/tests/VoronoiCrashTest.cpp index 9e2423a90f..f1e15b5a19 100644 --- a/tests/VoronoiCrashTest.cpp +++ b/tests/VoronoiCrashTest.cpp @@ -70,6 +70,8 @@ const std::vector polygon_filenames = { std::filesystem::path(__FILE__).parent_path().append("voronoi_crash_resources/slice_polygon_006.txt").string(), std::filesystem::path(__FILE__).parent_path().append("voronoi_crash_resources/slice_polygon_007.txt").string(), std::filesystem::path(__FILE__).parent_path().append("voronoi_crash_resources/slice_polygon_008.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("voronoi_crash_resources/slice_polygon_009.txt").string(), +// std::filesystem::path(__FILE__).parent_path().append("voronoi_crash_resources/slice_polygon_010.txt").string(), }; const std::vector setting_filenames = { @@ -81,6 +83,8 @@ const std::vector setting_filenames = { std::filesystem::path(__FILE__).parent_path().append("voronoi_crash_resources/settings_006.txt").string(), std::filesystem::path(__FILE__).parent_path().append("voronoi_crash_resources/settings_007.txt").string(), std::filesystem::path(__FILE__).parent_path().append("voronoi_crash_resources/settings_008.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("voronoi_crash_resources/settings_009.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("voronoi_crash_resources/settings_010.txt").string(), }; INSTANTIATE_TEST_SUITE_P(TestCrashingPolygons, VoronoiCrashTest, testing::Combine(testing::ValuesIn(polygon_filenames), testing::ValuesIn(setting_filenames)));