From 239d588c5dbc2a5f8edf698cacfff5aa3a2f8639 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 5 Nov 2020 17:32:40 +0100 Subject: [PATCH] 1) Implemented anchoring of infill lines to perimeters with length limited anchors, while before a full perimeter segment was always taken if possible. 2) Adapted the line infills (grid, stars, triangles, cubic) to 1). This also solves a long standing issue of these infills producing anchors for each sweep direction independently, thus possibly overlapping and overextruding, which was quite detrimental in narrow areas. 3) Refactored cubic adaptive infill anchroing algorithm for performance and clarity. --- src/libslic3r/EdgeGrid.cpp | 20 +- src/libslic3r/EdgeGrid.hpp | 1 + src/libslic3r/Fill/FillAdaptive.cpp | 340 ++++---- src/libslic3r/Fill/FillBase.cpp | 1044 ++++++++++++++++------- src/libslic3r/Fill/FillBase.hpp | 4 +- src/libslic3r/Fill/FillRectilinear2.cpp | 207 +++-- src/libslic3r/Fill/FillRectilinear2.hpp | 9 + src/libslic3r/Geometry.cpp | 6 +- src/libslic3r/Geometry.hpp | 136 ++- src/libslic3r/Point.hpp | 7 + src/libslic3r/libslic3r.h | 34 +- tests/libslic3r/test_geometry.cpp | 18 +- 12 files changed, 1200 insertions(+), 626 deletions(-) diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index 486a7b1aa..fa68092ee 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -55,6 +55,24 @@ void EdgeGrid::Grid::create(const Polygons &polygons, coord_t resolution) create_from_m_contours(resolution); } +void EdgeGrid::Grid::create(const std::vector &polygons, coord_t resolution) +{ + // Count the contours. + size_t ncontours = 0; + for (size_t j = 0; j < polygons.size(); ++ j) + if (! polygons[j]->points.empty()) + ++ ncontours; + + // Collect the contours. + m_contours.assign(ncontours, nullptr); + ncontours = 0; + for (size_t j = 0; j < polygons.size(); ++ j) + if (! polygons[j]->points.empty()) + m_contours[ncontours ++] = &polygons[j]->points; + + create_from_m_contours(resolution); +} + void EdgeGrid::Grid::create(const std::vector &polygons, coord_t resolution) { // Count the contours. @@ -1150,7 +1168,7 @@ EdgeGrid::Grid::ClosestPointResult EdgeGrid::Grid::closest_point(const Point &pt if (result.contour_idx != size_t(-1) && d_min <= double(search_radius)) { result.distance = d_min * sign_min; result.t /= l2_seg_min; - assert(result.t >= 0. && result.t < 1.); + assert(result.t >= 0. && result.t <= 1.); #ifndef NDEBUG { const Slic3r::Points &pts = *m_contours[result.contour_idx]; diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index 6a9f482a1..fc8e2c8ad 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -21,6 +21,7 @@ public: void set_bbox(const BoundingBox &bbox) { m_bbox = bbox; } void create(const Polygons &polygons, coord_t resolution); + void create(const std::vector &polygons, coord_t resolution); void create(const std::vector &polygons, coord_t resolution); void create(const ExPolygon &expoly, coord_t resolution); void create(const ExPolygons &expolygons, coord_t resolution); diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index b6f91b30c..65a7b95d6 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -553,21 +553,15 @@ static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines } #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ -static Matrix2d rotation_matrix_from_vector(const Point &vector) -{ - Matrix2d rotation; - rotation.block<1, 2>(0, 0) = vector.cast().normalized(); - rotation(1, 0) = -rotation(0, 1); - rotation(1, 1) = rotation(0, 0); - return rotation; -} - +// Representing a T-joint (in general case) between two infill lines +// (between one end point of intersect_pl/intersect_line and struct Intersection { // Index of the closest line to intersect_line size_t closest_line_idx; // Copy of closest line to intersect_point, used for storing original line in an unchanged state Line closest_line; + // Point for which is computed closest line (closest_line) Point intersect_point; // Index of the polyline from which is computed closest_line @@ -577,54 +571,53 @@ struct Intersection // The line for which is computed closest line from intersect_point to closest_line Line intersect_line; // Indicate if intersect_point is the first or the last point of intersect_pl - bool forward; + bool front; + // Indication if this intersection has been proceed bool used = false; - Intersection(const size_t closest_line_idx, - const Line &closest_line, - const Point &intersect_point, - size_t intersect_pl_idx, - Polyline *intersect_pl, - const Line &intersect_line, - bool forward) - : closest_line_idx(closest_line_idx) - , closest_line(closest_line) - , intersect_point(intersect_point) - , intersect_pl_idx(intersect_pl_idx) - , intersect_pl(intersect_pl) - , intersect_line(intersect_line) - , forward(forward) - {} + bool fresh() const throw() { return ! used && ! intersect_pl->empty(); } }; -static inline Intersection *get_nearest_intersection(std::vector> &intersect_line, const size_t first_idx) +static inline Intersection *get_nearest_intersection(std::vector> &intersect_line, const size_t first_idx) { assert(intersect_line.size() >= 2); + bool take_next = false; if (first_idx == 0) - return &intersect_line[first_idx + 1].first; - else if (first_idx == (intersect_line.size() - 1)) - return &intersect_line[first_idx - 1].first; - else if ((intersect_line[first_idx].second - intersect_line[first_idx - 1].second) < (intersect_line[first_idx + 1].second - intersect_line[first_idx].second)) - return &intersect_line[first_idx - 1].first; - else - return &intersect_line[first_idx + 1].first; + take_next = true; + else if (first_idx + 1 == intersect_line.size()) + take_next = false; + else { + // Has both prev and next. + const std::pair &ithis = intersect_line[first_idx]; + const std::pair &iprev = intersect_line[first_idx - 1]; + const std::pair &inext = intersect_line[first_idx + 1]; + take_next = iprev.first->fresh() && inext.first->fresh() ? + inext.second - ithis.second < ithis.second - iprev.second : + inext.first->fresh(); + } + return intersect_line[take_next ? first_idx + 1 : first_idx - 1].first; } -// Create a line based on line_to_offset translated it in the direction of the intersection line (intersection.intersect_line) +// Create a line representing the anchor aka hook extrusion based on line_to_offset +// translated in the direction of the intersection line (intersection.intersect_line). static Line create_offset_line(const Line &line_to_offset, const Intersection &intersection, const double scaled_spacing) { - Matrix2d rotation = rotation_matrix_from_vector(line_to_offset.vector()); - Vec2d offset_vector = ((scaled_spacing / 2.) * line_to_offset.normal().cast().normalized()); - Vec2d offset_line_point = line_to_offset.a.cast(); - Vec2d furthest_point = (intersection.intersect_point == intersection.intersect_line.a ? intersection.intersect_line.b : intersection.intersect_line.a).cast(); + Vec2d dir = line_to_offset.vector().cast().normalized(); + // 50% overlap of the extrusion lines to achieve strong bonding. + Vec2d offset_vector = Vec2d(- dir.y(), dir.x()) * (scaled_spacing / 2.); + const Point &furthest_point = (intersection.intersect_point == intersection.intersect_line.a ? intersection.intersect_line.b : intersection.intersect_line.a); - if ((rotation * furthest_point).y() >= (rotation * offset_line_point).y()) offset_vector *= -1; + // Move inside. + if (offset_vector.dot((furthest_point - intersection.intersect_point).cast()) < 0.) + offset_vector *= -1.; Line offset_line = line_to_offset; offset_line.translate(offset_vector.x(), offset_vector.y()); - // Extend the line by small value to guarantee a collision with adjacent lines + // Extend the line by a small value to guarantee a collision with adjacent lines offset_line.extend(coord_t(scale_(1.))); + //FIXME scaled_spacing * tan(PI/6) +// offset_line.extend(coord_t(scaled_spacing * 0.577)); return offset_line; }; @@ -637,26 +630,29 @@ using rtree_point_t = bgm::point; using rtree_segment_t = bgm::segment; using rtree_t = bgi::rtree, bgi::rstar<16, 4>>; +static inline rtree_point_t mk_rtree_point(const Point &pt) { + return rtree_point_t(float(pt.x()), float(pt.y())); +} static inline rtree_segment_t mk_rtree_seg(const Point &a, const Point &b) { - return { rtree_point_t(float(a.x()), float(a.y())), rtree_point_t(float(b.x()), float(b.y())) }; + return { mk_rtree_point(a), mk_rtree_point(b) }; } static inline rtree_segment_t mk_rtree_seg(const Line &l) { return mk_rtree_seg(l.a, l.b); } // Create a hook based on hook_line and append it to the begin or end of the polyline in the intersection -static void add_hook(const Intersection &intersection, const Line &hook_line, const double scaled_spacing, const int hook_length, const rtree_t &rtree) +static void add_hook(const Intersection &intersection, const double scaled_spacing, const int hook_length, const rtree_t &rtree) { - Vec2d hook_vector_norm = hook_line.vector().cast().normalized(); - Vector hook_vector = (hook_length * hook_vector_norm).cast(); - Line hook_line_offset = create_offset_line(hook_line, intersection, scaled_spacing); - - Point intersection_point; - bool intersection_found = intersection.intersect_line.intersection(hook_line_offset, &intersection_point); + // Trim the hook start by the infill line it will connect to. + Point hook_start; + bool intersection_found = intersection.intersect_line.intersection( + create_offset_line(intersection.closest_line, intersection, scaled_spacing), + &hook_start); assert(intersection_found); - Line hook_forward(intersection_point, intersection_point + hook_vector); - Line hook_backward(intersection_point, intersection_point - hook_vector); + Vec2d hook_vector_norm = intersection.closest_line.vector().cast().normalized(); + Vector hook_vector = (hook_length * hook_vector_norm).cast(); + Line hook_forward(hook_start, hook_start + hook_vector); auto filter_itself = [&intersection](const auto &item) { const rtree_segment_t &seg = item.first; @@ -666,51 +662,66 @@ static void add_hook(const Intersection &intersection, const Line &hook_line, co }; std::vector> hook_intersections; - rtree.query(bgi::intersects(mk_rtree_seg(hook_forward)) && bgi::satisfies(filter_itself), - std::back_inserter(hook_intersections)); + rtree.query(bgi::intersects(mk_rtree_seg(hook_forward)) && bgi::satisfies(filter_itself), std::back_inserter(hook_intersections)); - auto max_hook_length = [&hook_intersections, &hook_length](const Line &hook) { - coord_t max_length = hook_length; - for (const auto &hook_intersection : hook_intersections) { - const rtree_segment_t &segment = hook_intersection.first; - double dist = Line::distance_to(hook.a, Point(bg::get<0, 0>(segment), bg::get<0, 1>(segment)), - Point(bg::get<1, 0>(segment), bg::get<1, 1>(segment))); - max_length = std::min(coord_t(dist), max_length); - } - return max_length; - }; - - Line hook_final; + Point hook_end; if (hook_intersections.empty()) { - hook_final = std::move(hook_forward); + // The hook is not limited by another infill line. Extrude it in its full length. + hook_end = hook_forward.b; } else { - // There is not enough space for the hook, try another direction - coord_t hook_forward_max_length = max_hook_length(hook_forward); + + // Find closest intersection of a line segment starting with pt pointing in dir + // with any of the hook_intersections, returns Euclidian distance. + // dir is normalized. + auto max_hook_length = [hook_length](const Vec2d &pt, const Vec2d &dir, const std::vector> &hook_intersections) { + // No hook is longer than hook_length, there shouldn't be any intersection closer than that. + auto max_length = double(hook_length); + auto update_max_length = [&max_length](double d) { + if (d > 0. && d < max_length) + max_length = d; + }; + for (const auto &hook_intersection : hook_intersections) { + const rtree_segment_t &segment = hook_intersection.first; + // Segment start and end points. + Vec2d pt2(bg::get<0, 0>(segment), bg::get<0, 1>(segment)); + Vec2d pt2b(bg::get<1, 0>(segment), bg::get<1, 1>(segment)); + // Segment vector. + Vec2d dir2 = pt2b - pt2; + // Find intersection of (pt, dir) with (pt2, dir2), where dir is normalized. + double denom = cross2(dir, dir2); + if (std::abs(denom) < EPSILON) { + update_max_length((pt2 - pt).dot(dir)); + update_max_length((pt2b - pt).dot(dir)); + } else + update_max_length(cross2(pt2 - pt, dir2) / denom); + } + return max_length; + }; + + // There is not enough space for the full hook length, try the opposite direction. + Vec2d hook_startf = hook_start.cast(); + double hook_forward_max_length = max_hook_length(hook_startf, hook_vector_norm, hook_intersections); hook_intersections.clear(); - rtree.query(bgi::intersects(mk_rtree_seg(hook_backward)) && bgi::satisfies(filter_itself), - std::back_inserter(hook_intersections)); + Line hook_backward(hook_start, hook_start - hook_vector); + rtree.query(bgi::intersects(mk_rtree_seg(hook_backward)) && bgi::satisfies(filter_itself), std::back_inserter(hook_intersections)); if (hook_intersections.empty()) { - hook_final = std::move(hook_backward); + // The hook in the other direction is not limited by another infill line. Extrude it in its full length. + hook_end = hook_backward.b; } else { - // There is not enough space for hook in both directions, shrink the hook - coord_t hook_backward_max_length = max_hook_length(hook_backward); - if (hook_forward_max_length > hook_backward_max_length) { - Vector hook_vector_reduced = (hook_forward_max_length * hook_vector_norm).cast(); - hook_final = Line(intersection_point, intersection_point + hook_vector_reduced); - } else { - Vector hook_vector_reduced = (hook_backward_max_length * hook_vector_norm).cast(); - hook_final = Line(intersection_point, intersection_point - hook_vector_reduced); - } + // There is not enough space for the full hook in both directions, take the longer one. + double hook_backward_max_length = max_hook_length(hook_startf, - hook_vector_norm, hook_intersections); + Vec2d hook_dir = (hook_forward_max_length > hook_backward_max_length ? hook_forward_max_length : - hook_backward_max_length) * hook_vector_norm; + hook_end = hook_start + hook_dir.cast(); } } - if (intersection.forward) { - intersection.intersect_pl->points.front() = hook_final.a; - intersection.intersect_pl->points.emplace(intersection.intersect_pl->points.begin(), hook_final.b); + if (intersection.front) { + intersection.intersect_pl->points.front() = hook_start; + intersection.intersect_pl->points.emplace(intersection.intersect_pl->points.begin(), hook_end); } else { - intersection.intersect_pl->points.back() = hook_final.a; - intersection.intersect_pl->points.emplace_back(hook_final.b); + intersection.intersect_pl->points.back() = hook_start; + intersection.intersect_pl->points.emplace_back(hook_end); } } @@ -719,6 +730,7 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b rtree_t rtree; size_t poly_idx = 0; for (const Polyline &poly : lines) { + assert(poly.points.size() == 2); rtree.insert(std::make_pair(mk_rtree_seg(poly.points.front(), poly.points.back()), poly_idx++)); } @@ -731,24 +743,28 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b Polyline &line = lines[line_idx]; // Lines shorter than spacing are skipped because it is needed to shrink a line by the value of spacing. // A shorter line than spacing could produce a degenerate polyline. - if (line.length() <= (scaled_spacing + SCALED_EPSILON)) continue; + //FIXME we should rather remove such short infill lines earlier! + if (line.length() <= (scaled_spacing + SCALED_EPSILON)) + continue; - Point front_point = line.points.front(); - Point back_point = line.points.back(); + const Point &front_point = line.points.front(); + const Point &back_point = line.points.back(); auto filter_itself = [line_idx](const auto &item) { return item.second != line_idx; }; // Find the nearest line from the start point of the line. closest.clear(); - rtree.query(bgi::nearest(rtree_point_t(float(front_point.x()), float(front_point.y())), 1) && bgi::satisfies(filter_itself), std::back_inserter(closest)); - if (((Line) lines[closest[0].second]).distance_to(front_point) <= 1000) - intersections.emplace_back(closest[0].second, (Line) lines[closest[0].second], front_point, line_idx, &line, (Line) line, true); + rtree.query(bgi::nearest(mk_rtree_point(front_point), 1) && bgi::satisfies(filter_itself), std::back_inserter(closest)); + if (((Line) lines[closest.front().second]).distance_to(front_point) <= 1000) + // T-joint of line's front point with the 'closest' line. + intersections.push_back({ closest.front().second, (Line)lines[closest.front().second], front_point, line_idx, &line, (Line)line, true }); // Find the nearest line from the end point of the line closest.clear(); - rtree.query(bgi::nearest(rtree_point_t(float(back_point.x()), float(back_point.y())), 1) && bgi::satisfies(filter_itself), std::back_inserter(closest)); - if (((Line) lines[closest[0].second]).distance_to(back_point) <= 1000) - intersections.emplace_back(closest[0].second, (Line) lines[closest[0].second], back_point, line_idx, &line, (Line) line, false); + rtree.query(bgi::nearest(mk_rtree_point(back_point), 1) && bgi::satisfies(filter_itself), std::back_inserter(closest)); + if (((Line) lines[closest.front().second]).distance_to(back_point) <= 1000) + // T-joint of line's back point with the 'closest' line. + intersections.push_back({ closest.front().second, (Line)lines[closest.front().second], back_point, line_idx, &line, (Line)line, false }); } } @@ -758,7 +774,7 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b std::vector merged_with(lines.size()); std::iota(merged_with.begin(), merged_with.end(), 0); - // Appends the boundary polygon with all holes to rtree for detection if hooks not crossing the boundary + // Appends the boundary polygon with all holes to rtree for detection to check whether hooks are not crossing the boundary { Point prev = boundary.contour.points.back(); for (const Point &point : boundary.contour.points) { @@ -788,107 +804,97 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b intersection.intersect_pl = &lines[intersection.intersect_pl_idx]; // After polylines are merged, it is necessary to update "forward" based on if intersect_point is the first or the last point of intersect_pl. - if (!intersection.used && !intersection.intersect_pl->points.empty()) - intersection.forward = (intersection.intersect_pl->points.front() == intersection.intersect_point); + if (intersection.fresh()) + intersection.front = intersection.intersect_pl->points.front() == intersection.intersect_point; }; - for (size_t min_idx = 0; min_idx < intersections.size(); ++min_idx) { - std::vector> intersect_line; - Matrix2d rotation = rotation_matrix_from_vector(intersections[min_idx].closest_line.vector()); - intersect_line.emplace_back(intersections[min_idx], (rotation * intersections[min_idx].intersect_point.cast()).x()); - // All the nearest points on the same line are projected on this line. Because of it, it can easily find the nearest point - for (size_t max_idx = min_idx + 1; max_idx < intersections.size(); ++max_idx) { - if (intersections[min_idx].closest_line_idx != intersections[max_idx].closest_line_idx) break; - - intersect_line.emplace_back(intersections[max_idx], (rotation * intersections[max_idx].intersect_point.cast()).x()); + // Keep intersect_line outside the loop, so it does not get reallocated. + std::vector> intersect_line; + for (size_t min_idx = 0; min_idx < intersections.size();) { + const Vec2d line_dir = intersections[min_idx].closest_line.vector().cast(); + intersect_line.clear(); + // All the nearest points (T-joints) ending at the same line are projected onto this line. Because of it, it can easily find the nearest point. + { + const Point &p0 = intersections[min_idx].intersect_point; + size_t max_idx = min_idx + 1; + intersect_line.emplace_back(&intersections[min_idx], 0.); + for (; max_idx < intersections.size() && intersections[min_idx].closest_line_idx == intersections[max_idx].closest_line_idx; ++max_idx) + intersect_line.emplace_back(&intersections[max_idx], line_dir.dot((intersections[max_idx].intersect_point - p0).cast())); min_idx = max_idx; } - - assert(!intersect_line.empty()); - if (intersect_line.size() <= 1) { - // On the adjacent line is only one intersection - Intersection &first_i = intersect_line.front().first; - if (first_i.used || first_i.intersect_pl->points.empty()) continue; - - add_hook(first_i, first_i.closest_line, scale_(spacing), hook_length, rtree); - first_i.used = true; + if (intersect_line.size() == 1) { + // Simple case: The current intersection is the only one touching its adjacent line. + Intersection &first_i = *intersect_line.front().first; + if (first_i.fresh()) { + // Try to connect left or right. If not enough space for hook_length, take the longer side. + add_hook(first_i, scale_(spacing), hook_length, rtree); + first_i.used = true; + } continue; } - assert(intersect_line.size() >= 2); + assert(intersect_line.size() > 1); + // Sort the intersections along line_dir. std::sort(intersect_line.begin(), intersect_line.end(), [](const auto &i1, const auto &i2) { return i1.second < i2.second; }); - for (size_t first_idx = 0; first_idx < intersect_line.size(); ++first_idx) { - Intersection &first_i = intersect_line[first_idx].first; - Intersection &nearest_i = *get_nearest_intersection(intersect_line, first_idx); + for (size_t first_idx = 0; first_idx < intersect_line.size(); ++ first_idx) { + Intersection &first_i = *intersect_line[first_idx].first; + if (! first_i.fresh()) + // The intersection has been processed, or the polyline has been merged to another polyline. + continue; + // Get the previous or next intersection on the same line, pick the closer one. + Intersection &nearest_i = *get_nearest_intersection(intersect_line, first_idx); update_merged_polyline(first_i); update_merged_polyline(nearest_i); - // The intersection has been processed, or the polyline has been merge to another polyline. - if (first_i.used || first_i.intersect_pl->points.empty()) continue; - // A line between two intersections points - Line intersection_line(first_i.intersect_point, nearest_i.intersect_point); - Line offset_line = create_offset_line(intersection_line, first_i, scale_(spacing)); - double intersection_line_length = intersection_line.length(); - + Line offset_line = create_offset_line(Line(first_i.intersect_point, nearest_i.intersect_point), first_i, scale_(spacing)); // Check if both intersections lie on the offset_line and simultaneously get their points of intersecting. // These points are used as start and end of the hook Point first_i_point, nearest_i_point; if (first_i.intersect_line.intersection(offset_line, &first_i_point) && nearest_i.intersect_line.intersection(offset_line, &nearest_i_point)) { - // Both intersections are so close that their polylines can be connected - if (!nearest_i.used && !nearest_i.intersect_pl->points.empty() && intersection_line_length <= 2 * hook_length) { + if (nearest_i.fresh() && (nearest_i_point - first_i_point).cast().squaredNorm() <= Slic3r::sqr(3. * hook_length)) { + // Both intersections are so close that their polylines can be connected. if (first_i.intersect_pl_idx == nearest_i.intersect_pl_idx) { - // Both intersections are on the same polyline - if (!first_i.forward) { std::swap(first_i_point, nearest_i_point); } - + // Both intersections are on the same polyline, that means a loop is being closed. + if (! first_i.front) + std::swap(first_i_point, nearest_i_point); first_i.intersect_pl->points.front() = first_i_point; first_i.intersect_pl->points.back() = nearest_i_point; + //FIXME trim the end of a closed loop a bit? first_i.intersect_pl->points.emplace(first_i.intersect_pl->points.begin(), nearest_i_point); } else { // Both intersections are on different polylines - Points merge_polyline_points; - size_t first_polyline_size = first_i.intersect_pl->points.size(); - size_t nearest_polyline_size = nearest_i.intersect_pl->points.size(); - merge_polyline_points.reserve(first_polyline_size + nearest_polyline_size); - - if (first_i.forward) { - if (nearest_i.forward) - for (auto it = nearest_i.intersect_pl->points.rbegin(); it != nearest_i.intersect_pl->points.rend(); ++it) - merge_polyline_points.emplace_back(*it); - else - for (const Point &point : nearest_i.intersect_pl->points) - merge_polyline_points.emplace_back(point); - - append(merge_polyline_points, std::move(first_i.intersect_pl->points)); - merge_polyline_points[nearest_polyline_size - 1] = nearest_i_point; - merge_polyline_points[nearest_polyline_size] = first_i_point; + Points &first_points = first_i.intersect_pl->points; + Points &second_points = nearest_i.intersect_pl->points; + first_points.reserve(first_points.size() + second_points.size()); + if (first_i.front) + std::reverse(first_points.begin(), first_points.end()); + first_points.back() = first_i_point; + first_points.emplace_back(nearest_i_point); + if (nearest_i.front) + first_points.insert(first_points.end(), second_points.begin() + 1, second_points.end()); + else + first_points.insert(first_points.end(), second_points.rbegin() + 1, second_points.rend()); + // Keep the polyline at the lower index slot. + if (first_i.intersect_pl_idx < nearest_i.intersect_pl_idx) { + second_points.clear(); + merged_with[nearest_i.intersect_pl_idx] = merged_with[first_i.intersect_pl_idx]; } else { - append(merge_polyline_points, std::move(first_i.intersect_pl->points)); - if (nearest_i.forward) - for (const Point &point : nearest_i.intersect_pl->points) - merge_polyline_points.emplace_back(point); - else - for (auto it = nearest_i.intersect_pl->points.rbegin(); it != nearest_i.intersect_pl->points.rend(); ++it) - merge_polyline_points.emplace_back(*it); - - merge_polyline_points[first_polyline_size - 1] = first_i_point; - merge_polyline_points[first_polyline_size] = nearest_i_point; + second_points = std::move(first_points); + first_points.clear(); + merged_with[first_i.intersect_pl_idx] = merged_with[nearest_i.intersect_pl_idx]; } - - merged_with[nearest_i.intersect_pl_idx] = merged_with[first_i.intersect_pl_idx]; - - nearest_i.intersect_pl->points.clear(); - first_i.intersect_pl->points = merge_polyline_points; } - - first_i.used = true; nearest_i.used = true; - } else { - add_hook(first_i, first_i.closest_line, scale_(spacing), hook_length, rtree); - first_i.used = true; - } + } else + // Try to connect left or right. If not enough space for hook_length, take the longer side. + add_hook(first_i, scale_(spacing), hook_length, rtree); + first_i.used = true; + } else { + // The first & last point should always be found. + assert(false); } } } diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 8e0e980e3..bd29594c0 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -3,8 +3,9 @@ #include "../ClipperUtils.hpp" #include "../EdgeGrid.hpp" #include "../Geometry.hpp" -#include "../Surface.hpp" +#include "../Point.hpp" #include "../PrintConfig.hpp" +#include "../Surface.hpp" #include "../libslic3r.h" #include "FillBase.hpp" @@ -79,7 +80,7 @@ Polylines Fill::fill_surface(const Surface *surface, const FillParams ¶ms) params, surface->thickness_layers, _infill_direction(surface), - expp[i], + std::move(expp[i]), polylines_out); return polylines_out; } @@ -524,36 +525,222 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, #else -struct ContourPointData { - ContourPointData(float param) : param(param) {} - // Eucleidean position of the contour point along the contour. - float param = 0.f; - // Was the segment starting with this contour point extruded? - bool segment_consumed = false; - // Was this point extruded over? - bool point_consumed = false; +// A single T joint of an infill line to a closed contour or one of its holes. +struct ContourIntersectionPoint { + // Contour and point on a contour where an infill line is connected to. + size_t contour_idx; + size_t point_idx; + // Eucleidean parameter of point_idx along its contour. + float param; + // Other intersection points along the same contour. If there is only a single T-joint on a contour + // with an intersection line, then the prev_on_contour and next_on_contour remain nulls. + ContourIntersectionPoint* prev_on_contour { nullptr }; + ContourIntersectionPoint* next_on_contour { nullptr }; + // Length of the contour not yet allocated to some extrusion path going back (clockwise), or masked out by some overlapping infill line. + float contour_not_taken_length_prev { std::numeric_limits::max() }; + // Length of the contour not yet allocated to some extrusion path going forward (counter-clockwise), or masked out by some overlapping infill line. + float contour_not_taken_length_next { std::numeric_limits::max() }; + // End point is consumed if an infill line connected to this T-joint was already connected left or right along the contour, + // or if the infill line was processed, but it was not possible to connect it left or right along the contour. + bool consumed { false }; + // Whether the contour was trimmed by an overlapping infill line, or whether part of this contour was connected to some infill line. + bool prev_trimmed { false }; + bool next_trimmed { false }; + + void consume_prev() { this->contour_not_taken_length_prev = 0.; this->prev_trimmed = true; this->consumed = true; } + void consume_next() { this->contour_not_taken_length_next = 0.; this->next_trimmed = true; this->consumed = true; } + + void trim_prev(const float new_len) { + if (new_len < this->contour_not_taken_length_prev) { + this->contour_not_taken_length_prev = new_len; + this->prev_trimmed = true; + } + } + void trim_next(const float new_len) { + if (new_len < this->contour_not_taken_length_next) { + this->contour_not_taken_length_next = new_len; + this->next_trimmed = true; + } + } + + // The end point of an infill line connected to this T-joint was not processed yet and a piece of the contour could be extruded going backwards. + bool could_take_prev() const throw() { return ! this->consumed && this->contour_not_taken_length_prev > SCALED_EPSILON; } + // The end point of an infill line connected to this T-joint was not processed yet and a piece of the contour could be extruded going forward. + bool could_take_next() const throw() { return ! this->consumed && this->contour_not_taken_length_next > SCALED_EPSILON; } + + // Could extrude a complete segment from this to this->prev_on_contour. + bool could_connect_prev() const throw() + { return ! this->consumed && this->prev_on_contour && ! this->prev_on_contour->consumed && ! this->prev_trimmed && ! this->prev_on_contour->next_trimmed; } + // Could extrude a complete segment from this to this->next_on_contour. + bool could_connect_next() const throw() + { return ! this->consumed && this->next_on_contour && ! this->next_on_contour->consumed && ! this->next_trimmed && ! this->next_on_contour->prev_trimmed; } }; -// Verify whether the contour from point idx_start to point idx_end could be taken (whether all segments along the contour were not yet extruded). -static bool could_take(const std::vector &contour_data, size_t idx_start, size_t idx_end) +// Distance from param1 to param2 when going counter-clockwise. +static inline float closed_contour_distance_ccw(float param1, float param2, float contour_length) { - assert(idx_start != idx_end); - for (size_t i = idx_start; i != idx_end; ) { - if (contour_data[i].segment_consumed || contour_data[i].point_consumed) - return false; - if (++ i == contour_data.size()) - i = 0; - } - return ! contour_data[idx_end].point_consumed; + float d = param2 - param1; + if (d < 0.f) + d += contour_length; + return d; +} + +// Distance from param1 to param2 when going clockwise. +static inline float closed_contour_distance_cw(float param1, float param2, float contour_length) +{ + return closed_contour_distance_ccw(param2, param1, contour_length); +} + +// Length along the contour from cp1 to cp2 going counter-clockwise. +float path_length_along_contour_ccw(const ContourIntersectionPoint *cp1, const ContourIntersectionPoint *cp2, float contour_length) +{ + assert(cp1 != nullptr); + assert(cp2 != nullptr); + assert(cp1->contour_idx == cp2->contour_idx); + assert(cp1 != cp2); + // Zero'th param is the length of the contour. + float param_lo = cp1->param; + float param_hi = cp2->param; + assert(param_lo >= 0.f && param_lo <= contour_length); + assert(param_hi >= 0.f && param_hi <= contour_length); + return cp1 < cp2 ? param_hi - param_lo : param_lo + contour_length - param_hi; +} + +// Lengths along the contour from cp1 to cp2 going CCW and going CW. +std::pair path_lengths_along_contour(const ContourIntersectionPoint *cp1, const ContourIntersectionPoint *cp2, float contour_length) +{ + // Zero'th param is the length of the contour. + float param_lo = cp1->param; + float param_hi = cp2->param; + assert(param_lo >= 0.f && param_lo <= contour_length); + assert(param_hi >= 0.f && param_hi <= contour_length); + bool reversed = false; + if (param_lo > param_hi) { + std::swap(param_lo, param_hi); + reversed = true; + } + auto out = std::make_pair(param_hi - param_lo, param_lo + contour_length - param_hi); + if (reversed) + std::swap(out.first, out.second); + return out; +} + +// Add contour points from interval (idx_start, idx_end> to polyline. +static inline void take_cw_full(Polyline &pl, const Points& contour, size_t idx_start, size_t idx_end) +{ + assert(! pl.empty() && pl.points.back() == contour[idx_start]); + size_t i = (idx_end == 0) ? contour.size() - 1 : idx_start - 1; + while (i != idx_end) { + pl.points.emplace_back(contour[i]); + if (i == 0) + i = contour.size(); + --i; + } + pl.points.emplace_back(contour[i]); +} + +// Add contour points from interval (idx_start, idx_end> to polyline, limited by the Eucleidean length taken. +static inline float take_cw_limited(Polyline &pl, const Points &contour, const std::vector ¶ms, size_t idx_start, size_t idx_end, float length_to_take) +{ + // If appending to an infill line, then the start point of a perimeter line shall match the end point of an infill line. + assert(pl.empty() || pl.points.back() == contour[idx_start]); + assert(contour.size() + 1 == params.size()); + // Length of the contour. + float length = params.back(); + // Parameter (length from contour.front()) for the first point. + float p0 = params[idx_start]; + // Current (2nd) point of the contour. + size_t i = (idx_start == 0) ? contour.size() - 1 : idx_start - 1; + // Previous point of the contour. + size_t iprev = idx_start; + // Length of the contour curve taken for iprev. + float lprev = 0.f; + + for (;;) { + float l = closed_contour_distance_cw(p0, params[i], length); + if (l >= length_to_take) { + // Trim the last segment. + double t = double(length_to_take - lprev) / (l - lprev); + pl.points.emplace_back(lerp(contour[iprev], contour[i], t)); + return length_to_take; + } + // Continue with the other segments. + pl.points.emplace_back(contour[i]); + if (i == idx_end) + return l; + iprev = i; + lprev = l; + if (i == 0) + i = contour.size(); + -- i; + } + assert(false); + return 0; +} + +// Add contour points from interval (idx_start, idx_end> to polyline. +static inline void take_ccw_full(Polyline &pl, const Points &contour, size_t idx_start, size_t idx_end) +{ + assert(! pl.empty() && pl.points.back() == contour[idx_start]); + size_t i = idx_start; + if (++ i == contour.size()) + i = 0; + while (i != idx_end) { + pl.points.emplace_back(contour[i]); + if (++ i == contour.size()) + i = 0; + } + pl.points.emplace_back(contour[i]); +} + +// Add contour points from interval (idx_start, idx_end> to polyline, limited by the Eucleidean length taken. +// Returns length of the contour taken. +static inline float take_ccw_limited(Polyline &pl, const Points &contour, const std::vector ¶ms, size_t idx_start, size_t idx_end, float length_to_take) +{ + // If appending to an infill line, then the start point of a perimeter line shall match the end point of an infill line. + assert(pl.empty() || pl.points.back() == contour[idx_start]); + assert(contour.size() + 1 == params.size()); + // Length of the contour. + float length = params.back(); + // Parameter (length from contour.front()) for the first point. + float p0 = params[idx_start]; + // Current (2nd) point of the contour. + size_t i = idx_start; + if (++ i == contour.size()) + i = 0; + // Previous point of the contour. + size_t iprev = idx_start; + // Length of the contour curve taken at iprev. + float lprev = 0.f; + for (;;) { + float l = closed_contour_distance_ccw(p0, params[i], length); + if (l >= length_to_take) { + // Trim the last segment. + double t = double(length_to_take - lprev) / (l - lprev); + pl.points.emplace_back(lerp(contour[iprev], contour[i], t)); + return length_to_take; + } + // Continue with the other segments. + pl.points.emplace_back(contour[i]); + if (i == idx_end) + return l; + iprev = i; + lprev = l; + if (++ i == contour.size()) + i = 0; + } + assert(false); + return 0; } // Connect end of pl1 to the start of pl2 using the perimeter contour. -// The idx_start and idx_end are ordered so that the connecting polyline points will be taken with increasing indices. -static void take(Polyline &pl1, Polyline &&pl2, const Points &contour, std::vector &contour_data, size_t idx_start, size_t idx_end, bool reversed) +// If clockwise, then a clockwise segment from idx_start to idx_end is taken, otherwise a counter-clockwise segment is being taken. +static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, size_t idx_start, size_t idx_end, bool clockwise) { #ifndef NDEBUG - size_t num_points_initial = pl1.points.size(); assert(idx_start != idx_end); + assert(pl1.size() >= 2); + assert(pl2.size() >= 2); #endif /* NDEBUG */ { @@ -564,34 +751,108 @@ static void take(Polyline &pl1, Polyline &&pl2, const Points &contour, std::vect pl1.points.reserve(pl1.points.size() + size_t(new_points) + pl2.points.size()); } - contour_data[idx_start].point_consumed = true; - contour_data[idx_start].segment_consumed = true; - contour_data[idx_end ].point_consumed = true; + if (clockwise) + take_cw_full(pl1, contour, idx_start, idx_end); + else + take_ccw_full(pl1, contour, idx_start, idx_end); - if (reversed) { - size_t i = (idx_end == 0) ? contour_data.size() - 1 : idx_end - 1; - while (i != idx_start) { - contour_data[i].point_consumed = true; - contour_data[i].segment_consumed = true; - pl1.points.emplace_back(contour[i]); - if (i == 0) - i = contour_data.size(); - -- i; - } - } else { - size_t i = idx_start; - if (++ i == contour_data.size()) - i = 0; - while (i != idx_end) { - contour_data[i].point_consumed = true; - contour_data[i].segment_consumed = true; - pl1.points.emplace_back(contour[i]); - if (++ i == contour_data.size()) - i = 0; - } - } + pl1.points.insert(pl1.points.end(), pl2.points.begin() + 1, pl2.points.end()); +} - append(pl1.points, std::move(pl2.points)); +static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise) +{ + assert(cp_start != cp_end); + take(pl1, pl2, contour, cp_start->point_idx, cp_end->point_idx, clockwise); + + // Mark the contour segments in between cp_start and cp_end as consumed. + if (clockwise) + std::swap(cp_start, cp_end); + if (cp_start->next_on_contour != cp_end) + for (auto *cp = cp_start->next_on_contour; cp->next_on_contour != cp_end; cp = cp->next_on_contour) { + cp->consume_prev(); + cp->consume_next(); + } + cp_start->consume_next(); + cp_end->consume_prev(); +} + +static void take_limited( + Polyline &pl1, const Points &contour, const std::vector ¶ms, + ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise, float take_max_length, float line_half_width) +{ +#ifndef NDEBUG + assert(cp_start != cp_end); + assert(pl1.size() >= 2); + assert(contour.size() + 1 == params.size()); +#endif /* NDEBUG */ + + if (! (clockwise ? cp_start->could_take_prev() : cp_start->could_take_next())) + return; + + assert(pl1.points.front() == contour[cp_start->point_idx] || pl1.points.back() == contour[cp_start->point_idx]); + bool add_at_start = pl1.points.front() == contour[cp_start->point_idx]; + Points pl_tmp; + if (add_at_start) { + pl_tmp = std::move(pl1.points); + pl1.points.clear(); + } + + { + // Reserve memory at pl1 for the perimeter segment. + // Pessimizing - take the complete segment. + int new_points = int(cp_end->point_idx) - int(cp_start->point_idx) - 1; + if (new_points < 0) + new_points += int(contour.size()); + pl1.points.reserve(pl1.points.size() + pl_tmp.size() + size_t(new_points)); + } + + float length = params.back(); + float length_to_go = take_max_length; + cp_start->consumed = true; + if (clockwise) { + // Going clockwise from cp_start to cp_end. + for (ContourIntersectionPoint *cp = cp_start; cp != cp_end; cp = cp->prev_on_contour) { + // Length of the segment from cp to cp->prev_on_contour. + float l = closed_contour_distance_cw(cp->param, cp->prev_on_contour->param, length); + length_to_go = std::min(length_to_go, cp->contour_not_taken_length_prev); + if (cp->prev_on_contour->consumed) + // Don't overlap with an already extruded infill line. + length_to_go = std::max(0.f, std::min(length_to_go, l - line_half_width)); + cp->consume_prev(); + if (l >= length_to_go) { + cp->prev_on_contour->trim_next(l - length_to_go); + take_cw_limited(pl1, contour, params, cp->point_idx, cp->prev_on_contour->point_idx, length_to_go); + break; + } else { + cp->prev_on_contour->trim_next(0.f); + take_cw_full(pl1, contour, cp->point_idx, cp->prev_on_contour->point_idx); + length_to_go -= l; + } + } + } else { + for (ContourIntersectionPoint *cp = cp_start; cp != cp_end; cp = cp->next_on_contour) { + float l = closed_contour_distance_ccw(cp->param, cp->next_on_contour->param, length); + length_to_go = std::min(length_to_go, cp->contour_not_taken_length_next); + if (cp->next_on_contour->consumed) + // Don't overlap with an already extruded infill line. + length_to_go = std::max(0.f, std::min(length_to_go, l - line_half_width)); + cp->consume_next(); + if (l >= length_to_go) { + cp->next_on_contour->trim_prev(l - length_to_go); + take_ccw_limited(pl1, contour, params, cp->point_idx, cp->next_on_contour->point_idx, length_to_go); + break; + } else { + cp->next_on_contour->trim_prev(0.f); + take_ccw_full(pl1, contour, cp->point_idx, cp->next_on_contour->point_idx); + length_to_go -= l; + } + } + } + + if (add_at_start) { + pl1.reverse(); + append(pl1.points, pl_tmp); + } } // Return an index of start of a segment and a point of the clipping point at distance from the end of polyline. @@ -657,69 +918,159 @@ static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, do return out; } -// Optimized version with the precalculated v1 = p1b - p1a and l1_2 = v1.squaredNorm(). -// Assumption: l1_2 < EPSILON. -static inline double segment_point_distance_squared(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &v1, const double l1_2, const Vec2d &p2) +// Calculate intersection of a line with a thick segment. +// Returns Eucledian parameters of the line / thick segment overlap. +static inline bool line_rounded_thick_segment_collision( + const Vec2d &line_a, const Vec2d &line_b, + const Vec2d &segment_a, const Vec2d &segment_b, const double offset, + std::pair &out_interval) { - assert(l1_2 > EPSILON); - Vec2d v12 = p2 - p1a; - double t = v12.dot(v1); - return (t <= 0. ) ? v12.squaredNorm() : - (t >= l1_2) ? (p2 - p1a).squaredNorm() : - ((t / l1_2) * v1 - v12).squaredNorm(); + const Vec2d line_v0 = line_b - line_a; + double lv = line_v0.squaredNorm(); + + const Vec2d segment_v = segment_b - segment_a; + const double segment_l = segment_v.norm(); + const double offset2 = offset * offset; + + bool intersects = false; + if (lv < SCALED_EPSILON * SCALED_EPSILON) + { + // Very short line vector. Just test whether the center point is inside the offset line. + Vec2d lpt = 0.5 * (line_a + line_b); + + if (segment_l > SCALED_EPSILON) { + intersects = (segment_a - lpt).squaredNorm() < offset2; + intersects |= (segment_b - lpt).squaredNorm() < offset2; + if (! intersects) { + + } + } else + intersects = (0.5 * (segment_a + segment_b) - lpt).squaredNorm() < offset2; + if (intersects) { + out_interval.first = 0.; + out_interval.second = sqrt(lv); + } + } + else + { + // Output interval. + double tmin = std::numeric_limits::max(); + double tmax = -tmin; + auto extend_interval = [&tmin, &tmax](double atmin, double atmax) { + tmin = std::min(tmin, atmin); + tmax = std::max(tmax, atmax); + }; + + // Intersections with the inflated segment end points. + auto ray_circle_intersection_interval_extend = [&extend_interval, &line_v0](const Vec2d &segment_pt, const double offset2, const Vec2d &line_pt, const Vec2d &line_vec) { + std::pair pts; + Vec2d p0 = line_pt - segment_pt; + double c = - line_pt.dot(p0); + if (Geometry::ray_circle_intersections_r2_lv2_c(offset2, line_vec.x(), line_vec.y(), line_vec.squaredNorm(), c, pts)) { + double tmin = (pts.first - p0).dot(line_v0); + double tmax = (pts.second - p0).dot(line_v0); + if (tmin > tmax) + std::swap(tmin, tmax); + tmin = std::max(tmin, 0.); + tmax = std::min(tmax, 1.); + if (tmin <= tmax) + extend_interval(tmin, tmax); + } + }; + + // Intersections with the inflated segment. + if (segment_l > SCALED_EPSILON) { + ray_circle_intersection_interval_extend(segment_a, offset2, line_a, line_v0); + ray_circle_intersection_interval_extend(segment_b, offset2, line_a, line_v0); + // Clip the line segment transformed into a coordinate space of the segment, + // where the segment spans (0, 0) to (segment_l, 0). + const Vec2d dir_x = segment_v / segment_l; + const Vec2d dir_y(- dir_x.y(), dir_x.x()); + std::pair interval; + if (Geometry::liang_barsky_line_clipping_interval( + Vec2d(line_a - segment_a), + Vec2d(line_v0.dot(dir_x), line_v0.dot(dir_y)), + BoundingBoxf(Vec2d(0., - offset), Vec2d(segment_l, offset)), + interval)) + extend_interval(interval.first, interval.second); + } else + ray_circle_intersection_interval_extend(0.5 * (segment_a + segment_b), offset, line_a, line_v0); + + intersects = tmin <= tmax; + if (intersects) { + lv = sqrt(lv); + out_interval.first = tmin * lv; + out_interval.second = tmax * lv; + } + } + + return intersects; } -static inline double segment_point_distance_squared(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &p2) +static inline bool inside_interval(float low, float high, float p) { - const Vec2d v = p1b - p1a; - const double l2 = v.squaredNorm(); - if (l2 < EPSILON) - // p1a == p1b - return (p2 - p1a).squaredNorm(); - return segment_point_distance_squared(p1a, p1b, v, v.squaredNorm(), p2); + return p >= low && p <= high; } -// Distance to the closest point of line. -static inline double min_distance_of_segments(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &p2a, const Vec2d &p2b) +static inline bool interval_inside_interval(float outer_low, float outer_high, float inner_low, float inner_high, float epsilon) { - Vec2d v1 = p1b - p1a; - double l1_2 = v1.squaredNorm(); - if (l1_2 < EPSILON) - // p1a == p1b: Return distance of p1a from the (p2a, p2b) segment. - return segment_point_distance_squared(p2a, p2b, p1a); + outer_low -= epsilon; + outer_high += epsilon; + return inside_interval(outer_low, outer_high, inner_low) && inside_interval(outer_low, outer_high, inner_high); +} - Vec2d v2 = p2b - p2a; - double l2_2 = v2.squaredNorm(); - if (l2_2 < EPSILON) - // p2a == p2b: Return distance of p2a from the (p1a, p1b) segment. - return segment_point_distance_squared(p1a, p1b, v1, l1_2, p2a); - - return std::min( - std::min(segment_point_distance_squared(p1a, p1b, v1, l1_2, p2a), segment_point_distance_squared(p1a, p1b, v1, l1_2, p2b)), - std::min(segment_point_distance_squared(p2a, p2b, v2, l2_2, p1a), segment_point_distance_squared(p2a, p2b, v2, l2_2, p1b))); +static inline bool cyclic_interval_inside_interval(float outer_low, float outer_high, float inner_low, float inner_high, float length) +{ + if (outer_low > outer_high) + outer_high += length; + if (inner_low > inner_high) + inner_high += length; + else if (inner_high < outer_low) { + inner_low += length; + inner_high += length; + } + return interval_inside_interval(outer_low, outer_high, inner_low, inner_high, float(SCALED_EPSILON)); } // Mark the segments of split boundary as consumed if they are very close to some of the infill line. void mark_boundary_segments_touching_infill( - const std::vector &boundary, - std::vector> &boundary_data, - const BoundingBox &boundary_bbox, - const Polylines &infill, - const double clip_distance, - const double distance_colliding) + // Boundary contour, along which the perimeter extrusions will be drawn. + const std::vector &boundary, + // Parametrization of boundary with Euclidian length. + const std::vector> &boundary_parameters, + // Intersections (T-joints) of the infill lines with the boundary. + std::vector> &boundary_intersections, + // Bounding box around the boundary. + const BoundingBox &boundary_bbox, + // Infill lines, either completely inside the boundary, or touching the boundary. + const Polylines &infill, + // How much of the infill ends should be ignored when marking the boundary segments? + const double clip_distance, + // Roughly width of the infill line. + const double distance_colliding) { + assert(boundary.size() == boundary_parameters.size()); +#ifndef NDEBUG + for (size_t i = 0; i < boundary.size(); ++ i) + assert(boundary[i].size() + 1 == boundary_parameters[i].size()); +#endif + EdgeGrid::Grid grid; grid.set_bbox(boundary_bbox); // Inflate the bounding box by a thick line width. - grid.create(boundary, clip_distance + scale_(10.)); + grid.create(boundary, std::max(clip_distance, distance_colliding) + scale_(10.)); + // Visitor for the EdgeGrid to trim boundary_intersections with existing infill lines. struct Visitor { - Visitor(const EdgeGrid::Grid &grid, const std::vector &boundary, std::vector> &boundary_data, const double dist2_max) : - grid(grid), boundary(boundary), boundary_data(boundary_data), dist2_max(dist2_max) {} + Visitor(const EdgeGrid::Grid &grid, + const std::vector &boundary, const std::vector> &boundary_parameters, std::vector> &boundary_intersections, + const double radius) : + grid(grid), boundary(boundary), boundary_parameters(boundary_parameters), boundary_intersections(boundary_intersections), radius(radius) {} - void init(const Vec2d &pt1, const Vec2d &pt2) { - this->pt1 = &pt1; - this->pt2 = &pt2; + // Init with a segment of an infill line. + void init(const Vec2d &infill_pt1, const Vec2d &infill_pt2) { + this->infill_pt1 = &infill_pt1; + this->infill_pt2 = &infill_pt2; } bool operator()(coord_t iy, coord_t ix) { @@ -730,16 +1081,42 @@ void mark_boundary_segments_touching_infill( auto segment = this->grid.segment(*it_contour_and_segment); const Vec2d seg_pt1 = segment.first.cast(); const Vec2d seg_pt2 = segment.second.cast(); - if (min_distance_of_segments(seg_pt1, seg_pt2, *this->pt1, *this->pt2) < this->dist2_max) { - // Mark this boundary segment as touching the infill line. - ContourPointData &bdp = boundary_data[it_contour_and_segment->first][it_contour_and_segment->second]; - bdp.segment_consumed = true; - // There is no need for checking seg_pt2 as it will be checked the next time. - bool point_touching = false; - if (segment_point_distance_squared(*this->pt1, *this->pt2, seg_pt1) < this->dist2_max) { - point_touching = true; - bdp.point_consumed = true; - } + std::pair interval; + if (line_rounded_thick_segment_collision(seg_pt1, seg_pt2, *this->infill_pt1, *this->infill_pt2, this->radius, interval)) { + // The boundary segment intersects with the infill segment thickened by radius. + // Interval is specified in Euclidian length from seg_pt1 to seg_pt2. + // 1) Find the Euclidian parameters of seg_pt1 and seg_pt2 on its boundary contour. + const std::vector &contour_parameters = boundary_parameters[it_contour_and_segment->first]; + const float contour_length = contour_parameters.back(); + const float param_seg_pt1 = contour_parameters[it_contour_and_segment->second]; + const float param_overlap1 = param_seg_pt1 + interval.first; + const float param_overlap2 = param_seg_pt1 + interval.second; + // 2) Find the ContourIntersectionPoints before param_overlap1 and after param_overlap2. + std::vector &intersections = boundary_intersections[it_contour_and_segment->first]; + // Find the span of ContourIntersectionPoints, that is trimmed by the interval (param_overlap1, param_overlap2). + ContourIntersectionPoint *ip_low, *ip_high; + { + auto it_low = Slic3r::lower_bound_by_predicate(intersections.begin(), intersections.end(), [param_overlap1](const ContourIntersectionPoint *l) { return l->param < param_overlap1; }); + auto it_high = Slic3r::lower_bound_by_predicate(intersections.begin(), intersections.end(), [param_overlap2](const ContourIntersectionPoint *l) { return l->param < param_overlap2; }); + ip_low = it_low == intersections.end() ? intersections.front() : *it_low; + ip_high = it_high == intersections.end() ? intersections.front() : *it_high; + if (ip_low->param != param_overlap1) + ip_low = ip_low->prev_on_contour; + } + assert(ip_low != ip_high); + // Verify that the interval (param_overlap1, param_overlap2) is inside the interval (ip_low->param, ip_high->param). + assert(cyclic_interval_inside_interval(ip_low->param, ip_high->param, param_overlap1, param_overlap2, contour_length)); + // Mark all ContourIntersectionPoints between ip_low and ip_high as consumed. + if (ip_low->next_on_contour != ip_high) + for (ContourIntersectionPoint *ip = ip_low->next_on_contour; ip->next_on_contour != ip_high; ip = ip->next_on_contour) { + ip->consume_prev(); + ip->consume_next(); + } + // Subtract the interval from the first and last segments. + ip_low->trim_next(closed_contour_distance_ccw(ip_low->param, param_overlap1, contour_length)); + ip_high->trim_prev(closed_contour_distance_ccw(param_overlap2, ip_high->param, contour_length)); + //FIXME mark point as consumed? + //FIXME verify the sequence between prev and next? #if 0 { static size_t iRun = 0; @@ -758,15 +1135,16 @@ void mark_boundary_segments_touching_infill( return true; } - const EdgeGrid::Grid &grid; - const std::vector &boundary; - std::vector> &boundary_data; + const EdgeGrid::Grid &grid; + const std::vector &boundary; + const std::vector> &boundary_parameters; + std::vector> &boundary_intersections; // Maximum distance between the boundary and the infill line allowed to consider the boundary not touching the infill line. - const double dist2_max; + const double radius; - const Vec2d *pt1; - const Vec2d *pt2; - } visitor(grid, boundary, boundary_data, distance_colliding * distance_colliding); + const Vec2d *infill_pt1; + const Vec2d *infill_pt2; + } visitor(grid, boundary, boundary_parameters, boundary_intersections, distance_colliding); BoundingBoxf bboxf(boundary_bbox.min.cast(), boundary_bbox.max.cast()); bboxf.offset(- SCALED_EPSILON); @@ -832,89 +1210,46 @@ void mark_boundary_segments_touching_infill( } } -#if 0 -static double compute_distance_to_consumed_point(const std::vector & boundary, - const std::vector> &boundary_data, - size_t contour_idx, - size_t point_index, - bool forward) -{ - Point predecessor = boundary[contour_idx][point_index]; - double total_distance = 0; - - do { - if (forward) - point_index = (point_index == (boundary[contour_idx].size() - 1)) ? 0 : (point_index + 1); - else - point_index = (point_index > 0) ? (point_index - 1) : (boundary[contour_idx].size() - 1); - - Point successor = boundary[contour_idx][point_index]; - total_distance += (successor - predecessor).cast().norm(); - predecessor = successor; - } while (!boundary_data[contour_idx][point_index].point_consumed); - - return total_distance; -} -#endif - -// Returns possible path for an added hook. The path shrinks to max_lenght, by the closest consumed point or by the closest point in not_connected -// Also returns not shrink path's length to closest consumed point or closest point in not_connected -static std::pair get_hook_path(const std::vector &boundary, - const std::vector> &boundary_data, - size_t contour_idx, - size_t point_index, - bool forward, - int hook_length, - std::unordered_set ¬_connected) -{ - double total_distance = 0; - - Points points; - points.emplace_back(boundary[contour_idx][point_index]); - - // Follow the path around the boundary to consumed point or to the point in not_connected - do { - if (forward) - point_index = (point_index == (boundary[contour_idx].size() - 1)) ? 0 : (point_index + 1); - else - point_index = (point_index > 0) ? (point_index - 1) : (boundary[contour_idx].size() - 1); - - Point successor = boundary[contour_idx][point_index]; - total_distance += (successor - points.back()).cast().norm(); - points.emplace_back(successor); - } while (!boundary_data[contour_idx][point_index].point_consumed && total_distance < hook_length && - not_connected.find(points.back()) == not_connected.end()); - - // If the path is longer than hook_length, shrink it to this its length - if (total_distance > hook_length) { - Vec2d vector = (points.back() - points[points.size() - 2]).cast(); - double vector_length = vector.norm(); - double shrink_vec_length = vector_length - (total_distance - hook_length); - - points.back() = ((vector / vector_length) * shrink_vec_length).cast() + points[points.size() - 2]; - // total_distance += (shrink_vec_length - vector_length); - } - - return std::make_pair(points, total_distance); -} - void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const double spacing, const FillParams ¶ms, const int hook_length) { - assert(! infill_ordered.empty()); assert(! boundary_src.contour.points.empty()); + BoundingBox bbox = get_extents(boundary_src.contour); + bbox.offset(SCALED_EPSILON); - BoundingBox bbox = get_extents(boundary_src.contour); - bbox.offset(SCALED_EPSILON); + auto polygons_src = reserve_vector(boundary_src.holes.size() + 1); + polygons_src.emplace_back(&boundary_src.contour); + for (const Polygon &polygon : boundary_src.holes) + polygons_src.emplace_back(&polygon); + + connect_infill(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params, hook_length); +} + +void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms, const int hook_length) +{ + auto polygons_src = reserve_vector(boundary_src.size()); + for (const Polygon &polygon : boundary_src) + polygons_src.emplace_back(&polygon); + + connect_infill(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params, hook_length); +} + +void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms, const int hook_length) +{ + assert(! infill_ordered.empty()); + +#if 0 + append(polylines_out, infill_ordered); + return; +#endif // 1) Add the end points of infill_ordered to boundary_src. - std::vector boundary; - std::vector> boundary_data; - boundary.assign(boundary_src.holes.size() + 1, Points()); - boundary_data.assign(boundary_src.holes.size() + 1, std::vector()); + std::vector boundary; + std::vector> boundary_params; + boundary.assign(boundary_src.size(), Points()); + boundary_params.assign(boundary_src.size(), std::vector()); // Mapping the infill_ordered end point to a (contour, point) of boundary. - std::vector> map_infill_end_point_to_boundary; - static constexpr auto boundary_idx_unconnected = std::numeric_limits::max(); - map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair(boundary_idx_unconnected, boundary_idx_unconnected)); + static constexpr auto boundary_idx_unconnected = std::numeric_limits::max(); + std::vector map_infill_end_point_to_boundary(infill_ordered.size() * 2, ContourIntersectionPoint{ boundary_idx_unconnected, boundary_idx_unconnected }); { // Project the infill_ordered end points onto boundary_src. std::vector> intersection_points; @@ -941,54 +1276,96 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ } auto it = intersection_points.begin(); auto it_end = intersection_points.end(); - for (size_t idx_contour = 0; idx_contour <= boundary_src.holes.size(); ++ idx_contour) { - const Polygon &contour_src = (idx_contour == 0) ? boundary_src.contour : boundary_src.holes[idx_contour - 1]; + std::vector> boundary_intersection_points(boundary.size(), std::vector()); + for (size_t idx_contour = 0; idx_contour < boundary_src.size(); ++ idx_contour) { + // Copy contour_src to contour_dst while adding intersection points. + // Map infill end points map_infill_end_point_to_boundary to the newly inserted boundary points of contour_dst. + // chain the points of map_infill_end_point_to_boundary along their respective contours. + const Polygon &contour_src = *boundary_src[idx_contour]; Points &contour_dst = boundary[idx_contour]; + std::vector &contour_intersection_points = boundary_intersection_points[idx_contour]; + ContourIntersectionPoint *pfirst = nullptr; + ContourIntersectionPoint *pprev = nullptr; + { + // Reserve intersection points. + size_t n_intersection_points = 0; + for (auto itx = it; itx != it_end && itx->first.contour_idx == idx_contour; ++ itx) + ++ n_intersection_points; + contour_intersection_points.reserve(n_intersection_points); + } for (size_t idx_point = 0; idx_point < contour_src.points.size(); ++ idx_point) { contour_dst.emplace_back(contour_src.points[idx_point]); for (; it != it_end && it->first.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) { // Add these points to the destination contour. - const Vec2d pt1 = contour_src[idx_point].cast(); - const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast(); - const Vec2d pt = lerp(pt1, pt2, it->first.t); - map_infill_end_point_to_boundary[it->second] = std::make_pair(idx_contour, contour_dst.size()); - contour_dst.emplace_back(pt.cast()); +#ifndef NDEBUG + const Polyline &infill_line = infill_ordered[it->second / 2]; + const Point &pt = (it->second & 1) ? infill_line.points.back() : infill_line.points.front(); + { + const Vec2d pt1 = contour_src[idx_point].cast(); + const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast(); + const Vec2d ptx = lerp(pt1, pt2, it->first.t); + assert(std::abs(pt.x() - pt.x()) < SCALED_EPSILON); + assert(std::abs(pt.y() - pt.y()) < SCALED_EPSILON); + } +#endif // NDEBUG + map_infill_end_point_to_boundary[it->second] = ContourIntersectionPoint{ idx_contour, contour_dst.size() }; + ContourIntersectionPoint *pthis = &map_infill_end_point_to_boundary[it->second]; + if (pprev) { + pprev->next_on_contour = pthis; + pthis->prev_on_contour = pprev; + } else + pfirst = pthis; + contour_intersection_points.emplace_back(pthis); + pprev = pthis; + //add new point here + contour_dst.emplace_back(pt); } + if (pprev != pfirst) { + pprev->next_on_contour = pfirst; + pfirst->prev_on_contour = pprev; + } } - // Parametrize the curve. - std::vector &contour_data = boundary_data[idx_contour]; - contour_data.reserve(contour_dst.size()); - contour_data.emplace_back(ContourPointData(0.f)); + // Parametrize the new boundary with the intersection points inserted. + std::vector &contour_params = boundary_params[idx_contour]; + contour_params.assign(contour_dst.size() + 1, 0.f); for (size_t i = 1; i < contour_dst.size(); ++ i) - contour_data.emplace_back(contour_data.back().param + (contour_dst[i].cast() - contour_dst[i - 1].cast()).norm()); - contour_data.front().param = contour_data.back().param + (contour_dst.back().cast() - contour_dst.front().cast()).norm(); + contour_params[i] = contour_params[i - 1] + (contour_dst[i].cast() - contour_dst[i - 1].cast()).norm(); + contour_params.back() = contour_params[contour_params.size() - 2] + (contour_dst.back().cast() - contour_dst.front().cast()).norm(); + // Map parameters from contour_params to boundary_intersection_points. + for (ContourIntersectionPoint *ip : contour_intersection_points) + ip->param = contour_params[ip->point_idx]; + // and measure distance to the previous and next intersection point. + const float contour_length = contour_params.back(); + for (ContourIntersectionPoint *ip : contour_intersection_points) { + ip->contour_not_taken_length_prev = closed_contour_distance_ccw(ip->prev_on_contour->param, ip->param, contour_length); + ip->contour_not_taken_length_next = closed_contour_distance_ccw(ip->param, ip->next_on_contour->param, contour_length); + } } - assert(boundary.size() == boundary_src.num_contours()); + assert(boundary.size() == boundary_src.size()); #if 0 // Adaptive Cubic Infill produces infill lines, which not always end at the outer boundary. assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), - [&boundary](const std::pair &contour_point) { - return contour_point.first < boundary.size() && contour_point.second < boundary[contour_point.first].size(); + [&boundary](const ContourIntersectionPoint &contour_point) { + return contour_point.contour_idx < boundary.size() && contour_point.point_idx < boundary[contour_point.contour_idx].size(); })); #endif - } - // Mark the points and segments of split boundary as consumed if they are very close to some of the infill line. - { - // @supermerill used 2. * scale_(spacing) - const double clip_distance = 3. * scale_(spacing); - const double distance_colliding = 1.1 * scale_(spacing); - mark_boundary_segments_touching_infill(boundary, boundary_data, bbox, infill_ordered, clip_distance, distance_colliding); + // Mark the points and segments of split boundary as consumed if they are very close to some of the infill line. + { + // @supermerill used 2. * scale_(spacing) + const double clip_distance = 3. * scale_(spacing); + const double distance_colliding = 1.1 * scale_(spacing); + mark_boundary_segments_touching_infill(boundary, boundary_params, boundary_intersection_points, bbox, infill_ordered, clip_distance, distance_colliding); + } } // Connection from end of one infill line to the start of another infill line. //const float length_max = scale_(spacing); -// const float length_max = scale_((2. / params.density) * spacing); - const float length_max = scale_((1000. / params.density) * spacing); +// const auto length_max = float(scale_((2. / params.density) * spacing)); + const auto length_max = float(scale_((1000. / params.density) * spacing)); std::vector merged_with(infill_ordered.size()); - for (size_t i = 0; i < merged_with.size(); ++ i) - merged_with[i] = i; + std::iota(merged_with.begin(), merged_with.end(), 0); struct ConnectionCost { ConnectionCost(size_t idx_first, double cost, bool reversed) : idx_first(idx_first), cost(cost), reversed(reversed) {} size_t idx_first; @@ -1000,136 +1377,149 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) { const Polyline &pl1 = infill_ordered[idx_chain - 1]; const Polyline &pl2 = infill_ordered[idx_chain]; - const std::pair *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; - const std::pair *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2]; - if (cp1->first != boundary_idx_unconnected && cp1->first == cp2->first) { + const ContourIntersectionPoint *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; + const ContourIntersectionPoint *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2]; + if (cp1->contour_idx != boundary_idx_unconnected && cp1->contour_idx == cp2->contour_idx) { // End points on the same contour. Try to connect them. - const std::vector &contour_data = boundary_data[cp1->first]; - float param_lo = (cp1->second == 0) ? 0.f : contour_data[cp1->second].param; - float param_hi = (cp2->second == 0) ? 0.f : contour_data[cp2->second].param; - float param_end = contour_data.front().param; - bool reversed = false; - if (param_lo > param_hi) { - std::swap(param_lo, param_hi); - reversed = true; - } - assert(param_lo >= 0.f && param_lo <= param_end); - assert(param_hi >= 0.f && param_hi <= param_end); - double len = param_hi - param_lo; - if (len < length_max) - connections_sorted.emplace_back(idx_chain - 1, len, reversed); - len = param_lo + param_end - param_hi; - if (len < length_max) - connections_sorted.emplace_back(idx_chain - 1, len, ! reversed); + std::pair len = path_lengths_along_contour(cp1, cp2, boundary_params[cp1->contour_idx].back()); + if (len.first < length_max) + connections_sorted.emplace_back(idx_chain - 1, len.first, false); + if (len.second < length_max) + connections_sorted.emplace_back(idx_chain - 1, len.second, true); } } std::sort(connections_sorted.begin(), connections_sorted.end(), [](const ConnectionCost& l, const ConnectionCost& r) { return l.cost < r.cost; }); - size_t idx_chain_last = 0; - for (ConnectionCost &connection_cost : connections_sorted) { - const std::pair *cp1 = &map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1]; - const std::pair *cp1prev = cp1 - 1; - const std::pair *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; - const std::pair *cp2next = cp2 + 1; - assert(cp1->first == cp2->first && cp1->first != boundary_idx_unconnected); - std::vector &contour_data = boundary_data[cp1->first]; - if (connection_cost.reversed) - std::swap(cp1, cp2); - // Mark the the other end points of the segments to be taken as consumed temporarily, so they will not be crossed - // by the new connection line. - bool prev_marked = false; - bool next_marked = false; - if (cp1prev->first == cp1->first && ! contour_data[cp1prev->second].point_consumed) { - contour_data[cp1prev->second].point_consumed = true; - prev_marked = true; - } - if (cp2next->first == cp1->first && ! contour_data[cp2next->second].point_consumed) { - contour_data[cp2next->second].point_consumed = true; - next_marked = true; - } - if (could_take(contour_data, cp1->second, cp2->second)) { - // Indices of the polygons to be connected. - size_t idx_first = connection_cost.idx_first; - size_t idx_second = idx_first + 1; - for (size_t last = idx_first;;) { - size_t lower = merged_with[last]; - if (lower == last) { - merged_with[idx_first] = lower; - idx_first = lower; - break; - } - last = lower; - } - // Connect the two polygons using the boundary contour. - take(infill_ordered[idx_first], std::move(infill_ordered[idx_second]), boundary[cp1->first], contour_data, cp1->second, cp2->second, connection_cost.reversed); - // Mark the second polygon as merged with the first one. - merged_with[idx_second] = merged_with[idx_first]; - } - if (prev_marked) - contour_data[cp1prev->second].point_consumed = false; - if (next_marked) - contour_data[cp2next->second].point_consumed = false; - } - - auto get_merged_index = [&merged_with](size_t polyline_idx) { + auto get_and_update_merged_with = [&merged_with](size_t polyline_idx) -> size_t { for (size_t last = polyline_idx;;) { size_t lower = merged_with[last]; + assert(lower <= last); if (lower == last) { - merged_with[polyline_idx] = lower; - polyline_idx = lower; - break; + merged_with[polyline_idx] = last; + return last; } last = lower; } - - return polyline_idx; + assert(false); + return std::numeric_limits::max(); }; - if (hook_length != 0) { - // Create a set of points which has not been connected by the previous part of the algorithm - std::unordered_set not_connect_points; - for (const std::pair &contour_point : map_infill_end_point_to_boundary) - if (contour_point.first != boundary_idx_unconnected && !boundary_data[contour_point.first][contour_point.second].point_consumed) - not_connect_points.emplace(boundary[contour_point.first][contour_point.second]); - - for (size_t endpoint_idx = 0; endpoint_idx < map_infill_end_point_to_boundary.size(); ++endpoint_idx) { - Polyline &polyline = infill_ordered[get_merged_index(endpoint_idx / 2)]; - const std::pair &contour_point = map_infill_end_point_to_boundary[endpoint_idx]; - - if (contour_point.first != boundary_idx_unconnected && !boundary_data[contour_point.first][contour_point.second].point_consumed) { - Point boundary_point = boundary[contour_point.first][contour_point.second]; - auto [points_forward, forward_free_length] = get_hook_path(boundary, boundary_data, contour_point.first, contour_point.second, true, - hook_length, not_connect_points); - Points hook_points; - // Check if the hook could fit in space in the direction of perimeters - if (forward_free_length >= hook_length) { - hook_points = std::move(points_forward); - } else { - auto [points_backward, backward_free_length] = get_hook_path(boundary, boundary_data, contour_point.first, contour_point.second, - false, hook_length, not_connect_points); - // Check if the hook could fit in space in the opposite direction of perimeters. - // In this direction could be another hook. Because of it, it is required free space of size at least 2 * hook_length - if (backward_free_length >= (2 * hook_length)) - hook_points = std::move(points_backward); - else - continue; - } - - // Identify if the front point or back point of the polyline is touching the boundary - if ((boundary_point - polyline.points.front()).cast().squaredNorm() <= (SCALED_EPSILON * SCALED_EPSILON)) { - Points merged_points; - merged_points.reserve(polyline.points.size() + hook_points.size() - 1); - - for (auto it = hook_points.rbegin(); it != hook_points.rend() - 1; ++it) merged_points.emplace_back(*it); - - append(merged_points, std::move(polyline.points)); - polyline.points = std::move(merged_points); - } else { - for (auto it = hook_points.begin() + 1; it != hook_points.end(); ++it) polyline.points.emplace_back(*it); + const float take_max_length = hook_length > 0.f ? hook_length : std::numeric_limits::max(); + const float line_half_width = 0.5f * scale_(spacing); + for (ConnectionCost &connection_cost : connections_sorted) { + ContourIntersectionPoint *cp1 = &map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1]; + ContourIntersectionPoint *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; + assert(cp1 != cp2); + assert(cp1->contour_idx == cp2->contour_idx && cp1->contour_idx != boundary_idx_unconnected); + if (cp1->consumed || cp2->consumed) + continue; + const float length = connection_cost.cost; + bool could_connect; + { + // cp1, cp2 sorted CCW. + ContourIntersectionPoint *cp_low = connection_cost.reversed ? cp2 : cp1; + ContourIntersectionPoint *cp_high = connection_cost.reversed ? cp1 : cp2; + assert(std::abs(length - closed_contour_distance_ccw(cp_low->param, cp_high->param, boundary_params[cp1->contour_idx].back())) < SCALED_EPSILON); + could_connect = ! cp_low->next_trimmed && ! cp_high->prev_trimmed; + if (! could_connect && cp_low->next_on_contour != cp_high) { + // Other end of cp1, may or may not be on the same contour as cp1. + const ContourIntersectionPoint* cp1prev = cp1 - 1; + // Other end of cp2, may or may not be on the same contour as cp2. + const ContourIntersectionPoint* cp2next = cp2 + 1; + for (auto *cp = cp_low->next_on_contour; cp->next_on_contour != cp_high; cp = cp->next_on_contour) { + if (cp->consumed || cp == cp1prev || cp == cp2next || cp->prev_trimmed || cp->next_trimmed) { + could_connect = false; + break; + } + } + } + } + // Indices of the polylines to be connected by a perimeter segment. + size_t idx_first = connection_cost.idx_first; + size_t idx_second = idx_first + 1; + idx_first = get_and_update_merged_with(idx_first); + assert(idx_first < idx_second); + assert(idx_second == merged_with[idx_second]); + if (could_connect && (hook_length == 0.f || length < hook_length * 2.5)) { + // Take the complete contour. + // Connect the two polygons using the boundary contour. + take(infill_ordered[idx_first], infill_ordered[idx_second], boundary[cp1->contour_idx], cp1, cp2, connection_cost.reversed); + // Mark the second polygon as merged with the first one. + merged_with[idx_second] = merged_with[idx_first]; + infill_ordered[idx_second].points.clear(); + } else { + // Try to connect cp1 resp. cp2 with a piece of perimeter line. + take_limited(infill_ordered[idx_first], boundary[cp1->contour_idx], boundary_params[cp1->contour_idx], cp1, cp2, connection_cost.reversed, take_max_length, line_half_width); + take_limited(infill_ordered[idx_second], boundary[cp1->contour_idx], boundary_params[cp1->contour_idx], cp2, cp1, ! connection_cost.reversed, take_max_length, line_half_width); + } + } + + // Connect the remaining open infill lines to the perimeter lines if possible. + for (ContourIntersectionPoint &contour_point : map_infill_end_point_to_boundary) + if (! contour_point.consumed && contour_point.contour_idx != boundary_idx_unconnected) { + const Points &contour = boundary[contour_point.contour_idx]; + const std::vector &contour_params = boundary_params[contour_point.contour_idx]; + const size_t contour_pt_idx = contour_point.point_idx; + + float lprev = contour_point.could_connect_prev() ? + path_length_along_contour_ccw(contour_point.prev_on_contour, &contour_point, contour_params.back()) : + std::numeric_limits::max(); + float lnext = contour_point.could_connect_next() ? + path_length_along_contour_ccw(&contour_point, contour_point.next_on_contour, contour_params.back()) : + std::numeric_limits::max(); + size_t polyline_idx = get_and_update_merged_with(((&contour_point - map_infill_end_point_to_boundary.data()) / 2)); + Polyline &polyline = infill_ordered[polyline_idx]; + assert(! polyline.empty()); + assert(contour[contour_point.point_idx] == polyline.points.front() || contour[contour_point.point_idx] == polyline.points.back()); + bool connected = false; + for (float l : { std::min(lprev, lnext), std::max(lprev, lnext) }) { + if (l == std::numeric_limits::max() || (hook_length > 0.f && l > hook_length * 2.5)) + break; + // Take the complete contour. + bool reversed = l == lprev; + ContourIntersectionPoint *cp2 = reversed ? contour_point.prev_on_contour : contour_point.next_on_contour; + // Identify which end of the polyline touches the boundary. + size_t polyline_idx2 = get_and_update_merged_with(((cp2 - map_infill_end_point_to_boundary.data()) / 2)); + if (polyline_idx == polyline_idx2) + // Try the other side. + continue; + // Not closing a loop. + if (contour[contour_point.point_idx] == polyline.points.front()) + polyline.reverse(); + Polyline &polyline2 = infill_ordered[polyline_idx2]; + assert(! polyline.empty()); + assert(contour[cp2->point_idx] == polyline2.points.front() || contour[cp2->point_idx] == polyline2.points.back()); + if (contour[cp2->point_idx] == polyline2.points.back()) + polyline2.reverse(); + take(polyline, polyline2, contour, &contour_point, cp2, reversed); + if (polyline_idx < polyline_idx2) { + // Mark the second polyline as merged with the first one. + merged_with[polyline_idx2] = polyline_idx; + polyline2.points.clear(); + } else { + // Mark the first polyline as merged with the second one. + merged_with[polyline_idx] = polyline_idx2; + polyline2 = std::move(polyline); + polyline.points.clear(); + } + connected = true; + break; + } + if (! connected) { + // Which to take? One could optimize for: + // 1) Shortest path + // 2) Hook length + // ... + // Let's take the longer now, as this improves the chance of another hook to be placed on the other side of this contour point. + float l = std::max(contour_point.contour_not_taken_length_prev, contour_point.contour_not_taken_length_next); + if (l > SCALED_EPSILON) { + if (contour_point.contour_not_taken_length_prev > contour_point.contour_not_taken_length_next) + take_limited(polyline, contour, contour_params, &contour_point, contour_point.prev_on_contour, true, take_max_length, line_half_width); + else + take_limited(polyline, contour, contour_params, &contour_point, contour_point.next_on_contour, false, take_max_length, line_half_width); } } } - } polylines_out.reserve(polylines_out.size() + std::count_if(infill_ordered.begin(), infill_ordered.end(), [](const Polyline &pl) { return ! pl.empty(); })); for (Polyline &pl : infill_ordered) diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 0df4bd6c1..87885e655 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -124,7 +124,9 @@ protected: virtual std::pair _infill_direction(const Surface *surface) const; public: - static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, double spacing, const FillParams ¶ms, const int hook_length = 0); + static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const double spacing, const FillParams ¶ms, const int hook_length = 0); + static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms, const int hook_length = 0); + static void connect_infill(Polylines &&infill_ordered, const std::vector &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams ¶ms, const int hook_length = 0); static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); diff --git a/src/libslic3r/Fill/FillRectilinear2.cpp b/src/libslic3r/Fill/FillRectilinear2.cpp index 493bb7c6f..a110dd144 100644 --- a/src/libslic3r/Fill/FillRectilinear2.cpp +++ b/src/libslic3r/Fill/FillRectilinear2.cpp @@ -7,12 +7,14 @@ #include #include +#include #include #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" #include "../Geometry.hpp" #include "../Surface.hpp" +#include "../ShortestPath.hpp" #include "FillRectilinear2.hpp" @@ -128,6 +130,13 @@ struct SegmentIntersection return coord_t(p / int64_t(pos_q)); } + // Left vertical line / contour intersection point. + // null if next_on_contour_vertical. + int32_t prev_on_contour { 0 }; + // Right vertical line / contour intersection point. + // If next_on_contour_vertical, then then next_on_contour contains next contour point on the same vertical line. + int32_t next_on_contour { 0 }; + // Kind of intersection. With the original contour, or with the inner offestted contour? // A vertical segment will be at least intersected by OUTER_LOW, OUTER_HIGH, // but it could be intersected with OUTER_LOW, INNER_LOW, INNER_HIGH, OUTER_HIGH, @@ -141,13 +150,6 @@ struct SegmentIntersection }; SegmentIntersectionType type { UNKNOWN }; - // Left vertical line / contour intersection point. - // null if next_on_contour_vertical. - int32_t prev_on_contour { 0 }; - // Right vertical line / contour intersection point. - // If next_on_contour_vertical, then then next_on_contour contains next contour point on the same vertical line. - int32_t next_on_contour { 0 }; - enum class LinkType : uint8_t { // Horizontal link (left or right). Horizontal, @@ -383,30 +385,31 @@ public: const ExPolygon &expolygon, float angle, coord_t aoffset1, - coord_t aoffset2) + // If the 2nd offset is zero, then it is ignored and only OUTER_LOW / OUTER_HIGH intersections are + // populated into vertical intersection lines. + coord_t aoffset2 = 0) { // Copy and rotate the source polygons. polygons_src = expolygon; - polygons_src.contour.rotate(angle); - for (Polygons::iterator it = polygons_src.holes.begin(); it != polygons_src.holes.end(); ++ it) - it->rotate(angle); + if (angle != 0.f) { + polygons_src.contour.rotate(angle); + for (Polygon &hole : polygons_src.holes) + hole.rotate(angle); + } double mitterLimit = 3.; // for the infill pattern, don't cut the corners. // default miterLimt = 3 //double mitterLimit = 10.; assert(aoffset1 < 0); - assert(aoffset2 < 0); - assert(aoffset2 < aoffset1); + assert(aoffset2 <= 0); + assert(aoffset2 == 0 || aoffset2 < aoffset1); // bool sticks_removed = remove_sticks(polygons_src); -// if (sticks_removed) printf("Sticks removed!\n"); - polygons_outer = offset(polygons_src, float(aoffset1), - ClipperLib::jtMiter, - mitterLimit); - polygons_inner = offset(polygons_outer, float(aoffset2 - aoffset1), - ClipperLib::jtMiter, - mitterLimit); +// if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!"; + polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, mitterLimit); + if (aoffset2 < 0) + polygons_inner = offset(polygons_outer, float(aoffset2 - aoffset1), ClipperLib::jtMiter, mitterLimit); // Filter out contours with zero area or small area, contours with 2 points only. const double min_area_threshold = 0.01 * aoffset2 * aoffset2; remove_small(polygons_outer, min_area_threshold); @@ -424,6 +427,18 @@ public: } } + ExPolygonWithOffset(const ExPolygonWithOffset &rhs, float angle) : ExPolygonWithOffset(rhs) { + if (angle != 0.f) { + this->polygons_src.contour.rotate(angle); + for (Polygon &hole : this->polygons_src.holes) + hole.rotate(angle); + for (Polygon &poly : this->polygons_outer) + poly.rotate(angle); + for (Polygon &poly : this->polygons_inner) + poly.rotate(angle); + } + } + // Any contour with offset1 bool is_contour_outer(size_t idx) const { return idx < n_contours_outer; } // Any contour with offset2 @@ -2644,7 +2659,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP Point refpt = rotate_vector.second.rotated(- rotate_vector.first); // _align_to_grid will not work correctly with positive pattern_shift. coord_t pattern_shift_scaled = coord_t(scale_(pattern_shift)) % line_spacing; - refpt(0) -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); + refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); bounding_box.merge(_align_to_grid( bounding_box.min, Point(line_spacing, line_spacing), @@ -2747,12 +2762,93 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP return true; } +#define FILL_MULTIPLE_SWEEPS_NEW + +#ifdef FILL_MULTIPLE_SWEEPS_NEW +bool FillRectilinear2::fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out) +{ + assert(sweep_params.size() > 1); + assert(! params.full_infill()); + params.density /= double(sweep_params.size()); + assert(params.density > 0.0001f && params.density <= 1.f); + + ExPolygonWithOffset poly_with_offset_base(surface->expolygon, 0, float(scale_(this->overlap - 0.5 * this->spacing))); + if (poly_with_offset_base.n_contours == 0) + // Not a single infill line fits. + return true; + + Polylines fill_lines; + coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); + std::pair rotate_vector = this->_infill_direction(surface); + for (const SweepParams &sweep : sweep_params) { + size_t n_fill_lines_initial = fill_lines.size(); + + // Rotate polygons so that we can work with vertical lines here + double angle = rotate_vector.first + sweep.angle_base; + ExPolygonWithOffset poly_with_offset(poly_with_offset_base, - angle); + BoundingBox bounding_box = poly_with_offset.bounding_box_src(); + // extend bounding box so that our pattern will be aligned with other layers + // Transform the reference point to the rotated coordinate system. + Point refpt = rotate_vector.second.rotated(- angle); + // _align_to_grid will not work correctly with positive pattern_shift. + coord_t pattern_shift_scaled = coord_t(scale_(sweep.pattern_shift)) % line_spacing; + refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); + bounding_box.merge(_align_to_grid(bounding_box.min, Point(line_spacing, line_spacing), refpt)); + + // Intersect a set of euqally spaced vertical lines wiht expolygon. + // n_vlines = ceil(bbox_width / line_spacing) + const size_t n_vlines = (bounding_box.max.x() - bounding_box.min.x() + line_spacing - 1) / line_spacing; + const double cos_a = cos(angle); + const double sin_a = sin(angle); + for (const SegmentedIntersectionLine &vline : slice_region_by_vertical_lines(poly_with_offset, n_vlines, bounding_box.min.x(), line_spacing)) { + for (auto it = vline.intersections.begin(); it != vline.intersections.end();) { + auto it_low = it ++; + assert(it_low->type == SegmentIntersection::OUTER_LOW); + if (it_low->type != SegmentIntersection::OUTER_LOW) + continue; + auto it_high = it; + assert(it_high->type == SegmentIntersection::OUTER_HIGH); + if (it_high->type == SegmentIntersection::OUTER_HIGH) { + fill_lines.emplace_back(Point(vline.pos, it_low->pos()).rotated(cos_a, sin_a), Point(vline.pos, it_high->pos()).rotated(cos_a, sin_a)); + ++ it; + } + } + } + } + + if (fill_lines.size() > 1) + fill_lines = chain_polylines(std::move(fill_lines)); + + if (params.dont_connect || fill_lines.size() <= 1) + append(polylines_out, std::move(fill_lines)); + else { +// coord_t hook_length = 0; + coord_t hook_length = coord_t(scale_(this->spacing)) * 5; + connect_infill(std::move(fill_lines), poly_with_offset_base.polygons_outer, get_extents(surface->expolygon.contour), polylines_out, this->spacing, params, hook_length); + } + + return true; +} +#else +bool FillRectilinear2::fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out) +{ + params.density /= double(sweep_params.size()); + bool success = true; + int idx = 0; + for (const SweepParams &sweep_param : sweep_params) { + if (++ idx == 3) + params.dont_connect = true; + success &= this->fill_surface_by_lines(surface, params, sweep_param.angle_base, sweep_param.pattern_shift, polylines_out); + } + return success; +} +#endif + Polylines FillRectilinear2::fill_surface(const Surface *surface, const FillParams ¶ms) { Polylines polylines_out; - if (! fill_surface_by_lines(surface, params, 0.f, 0.f, polylines_out)) { - printf("FillRectilinear2::fill_surface() failed to fill a region.\n"); - } + if (! fill_surface_by_lines(surface, params, 0.f, 0.f, polylines_out)) + BOOST_LOG_TRIVIAL(error) << "FillRectilinear2::fill_surface() failed to fill a region."; return polylines_out; } @@ -2761,72 +2857,53 @@ Polylines FillMonotonic::fill_surface(const Surface *surface, const FillParams & FillParams params2 = params; params2.monotonic = true; Polylines polylines_out; - if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out)) { - printf("FillMonotonic::fill_surface() failed to fill a region.\n"); - } + if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out)) + BOOST_LOG_TRIVIAL(error) << "FillMonotonous::fill_surface() failed to fill a region."; return polylines_out; } Polylines FillGrid2::fill_surface(const Surface *surface, const FillParams ¶ms) { - // Each linear fill covers half of the target coverage. - FillParams params2 = params; - params2.density *= 0.5f; Polylines polylines_out; - if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out) || - ! fill_surface_by_lines(surface, params2, float(M_PI / 2.), 0.f, polylines_out)) { - printf("FillGrid2::fill_surface() failed to fill a region.\n"); - } + if (! this->fill_surface_by_multilines( + surface, params, + { { 0.f, 0.f }, { float(M_PI / 2.), 0.f } }, + polylines_out)) + BOOST_LOG_TRIVIAL(error) << "FillGrid2::fill_surface() failed to fill a region."; return polylines_out; } Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams ¶ms) { - // Each linear fill covers 1/3 of the target coverage. - FillParams params2 = params; - params2.density *= 0.333333333f; - FillParams params3 = params2; - params3.dont_connect = true; Polylines polylines_out; - if (! fill_surface_by_lines(surface, params2, 0.f, 0., polylines_out) || - ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), 0., polylines_out) || - ! fill_surface_by_lines(surface, params3, float(2. * M_PI / 3.), 0., polylines_out)) { - printf("FillTriangles::fill_surface() failed to fill a region.\n"); - } + if (! this->fill_surface_by_multilines( + surface, params, + { { 0.f, 0.f }, { float(M_PI / 3.), 0.f }, { float(2. * M_PI / 3.), 0. } }, + polylines_out)) + BOOST_LOG_TRIVIAL(error) << "FillTriangles::fill_surface() failed to fill a region."; return polylines_out; } Polylines FillStars::fill_surface(const Surface *surface, const FillParams ¶ms) { - // Each linear fill covers 1/3 of the target coverage. - FillParams params2 = params; - params2.density *= 0.333333333f; - FillParams params3 = params2; - params3.dont_connect = true; Polylines polylines_out; - if (! fill_surface_by_lines(surface, params2, 0.f, 0., polylines_out) || - ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), 0., polylines_out) || - ! fill_surface_by_lines(surface, params3, float(2. * M_PI / 3.), 0.5 * this->spacing / params2.density, polylines_out)) { - printf("FillStars::fill_surface() failed to fill a region.\n"); - } + if (! this->fill_surface_by_multilines( + surface, params, + { { 0.f, 0.f }, { float(M_PI / 3.), 0.f }, { float(2. * M_PI / 3.), float((3./2.) * this->spacing / params.density) } }, + polylines_out)) + BOOST_LOG_TRIVIAL(error) << "FillStars::fill_surface() failed to fill a region."; return polylines_out; } Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶ms) { - // Each linear fill covers 1/3 of the target coverage. - FillParams params2 = params; - params2.density *= 0.333333333f; - FillParams params3 = params2; - params3.dont_connect = true; Polylines polylines_out; coordf_t dx = sqrt(0.5) * z; - if (! fill_surface_by_lines(surface, params2, 0.f, float(dx), polylines_out) || - ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), - float(dx), polylines_out) || - // Rotated by PI*2/3 + PI to achieve reverse sloping wall. - ! fill_surface_by_lines(surface, params3, float(M_PI * 2. / 3.), float(dx), polylines_out)) { - printf("FillCubic::fill_surface() failed to fill a region.\n"); - } + if (! this->fill_surface_by_multilines( + surface, params, + { { 0.f, float(dx) }, { float(M_PI / 3.), - float(dx) }, { float(M_PI * 2. / 3.), float(dx) } }, + polylines_out)) + BOOST_LOG_TRIVIAL(error) << "FillCubic::fill_surface() failed to fill a region."; return polylines_out; } diff --git a/src/libslic3r/Fill/FillRectilinear2.hpp b/src/libslic3r/Fill/FillRectilinear2.hpp index fd28f155d..1d8c61a94 100644 --- a/src/libslic3r/Fill/FillRectilinear2.hpp +++ b/src/libslic3r/Fill/FillRectilinear2.hpp @@ -17,7 +17,16 @@ public: virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); protected: + // Fill by single directional lines, interconnect the lines along perimeters. bool fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out); + + + // Fill by multiple sweeps of differing directions. + struct SweepParams { + float angle_base; + float pattern_shift; + }; + bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out); }; class FillMonotonic : public FillRectilinear2 diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 3b9fcd617..b263aecfd 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -338,19 +338,19 @@ double rad2deg_dir(double angle) return rad2deg(angle); } -Point circle_taubin_newton(const Points::const_iterator& input_begin, const Points::const_iterator& input_end, size_t cycles) +Point circle_center_taubin_newton(const Points::const_iterator& input_begin, const Points::const_iterator& input_end, size_t cycles) { Vec2ds tmp; tmp.reserve(std::distance(input_begin, input_end)); std::transform(input_begin, input_end, std::back_inserter(tmp), [] (const Point& in) { return unscale(in); } ); - Vec2d center = circle_taubin_newton(tmp.cbegin(), tmp.end(), cycles); + Vec2d center = circle_center_taubin_newton(tmp.cbegin(), tmp.end(), cycles); return Point::new_scale(center.x(), center.y()); } /// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126 /// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end /// lie on. -Vec2d circle_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles) +Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles) { // calculate the centroid of the data set const Vec2d sum = std::accumulate(input_begin, input_end, Vec2d(0,0)); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index b690b478d..2bf9453c4 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -201,6 +201,58 @@ inline double ray_point_distance(const Line &iline, const Point &ipt) } // Based on Liang-Barsky function by Daniel White @ http://www.skytopia.com/project/articles/compsci/clipping.html +template +inline bool liang_barsky_line_clipping_interval( + // Start and end points of the source line, result will be stored there as well. + const Eigen::Matrix &x0, + const Eigen::Matrix &v, + // Bounding box to clip with. + const BoundingBoxBase> &bbox, + std::pair &out_interval) +{ + double t0 = 0.0; + double t1 = 1.0; + // Traverse through left, right, bottom, top edges. + for (int edge = 0; edge < 4; ++ edge) + { + double p, q; + switch (edge) { + case 0: p = - v.x(); q = - bbox.min.x() + x0.x(); break; + case 1: p = v.x(); q = bbox.max.x() - x0.x(); break; + case 2: p = - v.y(); q = - bbox.min.y() + x0.y(); break; + default: p = v.y(); q = bbox.max.y() - x0.y(); break; + } + + if (p == 0) { + if (q < 0) + // Line parallel to the bounding box edge is fully outside of the bounding box. + return false; + // else don't clip + } else { + double r = q / p; + if (p < 0) { + if (r > t1) + // Fully clipped. + return false; + if (r > t0) + // Partially clipped. + t0 = r; + } else { + assert(p > 0); + if (r < t0) + // Fully clipped. + return false; + if (r < t1) + // Partially clipped. + t1 = r; + } + } + } + out_interval.first = t0; + out_interval.second = t1; + return true; +} + template inline bool liang_barsky_line_clipping( // Start and end points of the source line, result will be stored there as well. @@ -210,49 +262,12 @@ inline bool liang_barsky_line_clipping( const BoundingBoxBase> &bbox) { Eigen::Matrix v = x1 - x0; - double t0 = 0.0; - double t1 = 1.0; - - // Traverse through left, right, bottom, top edges. - for (int edge = 0; edge < 4; ++ edge) - { - double p, q; - switch (edge) { - case 0: p = - v.x(); q = - bbox.min.x() + x0.x(); break; - case 1: p = v.x(); q = bbox.max.x() - x0.x(); break; - case 2: p = - v.y(); q = - bbox.min.y() + x0.y(); break; - default: p = v.y(); q = bbox.max.y() - x0.y(); break; - } - - if (p == 0) { - if (q < 0) - // Line parallel to the bounding box edge is fully outside of the bounding box. - return false; - // else don't clip - } else { - double r = q / p; - if (p < 0) { - if (r > t1) - // Fully clipped. - return false; - if (r > t0) - // Partially clipped. - t0 = r; - } else { - assert(p > 0); - if (r < t0) - // Fully clipped. - return false; - if (r < t1) - // Partially clipped. - t1 = r; - } - } + std::pair interval; + if (liang_barsky_line_clipping_interval(x0, v, bbox, interval)) { + // Clipped successfully. + x1 = x0 + interval.second * v; + x0 += interval.first * v; } - - // Clipped successfully. - x1 = x0 + t1 * v; - x0 += t0 * v; return true; } @@ -273,6 +288,35 @@ bool liang_barsky_line_clipping( return liang_barsky_line_clipping(x0clip, x1clip, bbox); } +// Ugly named variant, that accepts the squared line +// Don't call me with a nearly zero length vector! +template +int ray_circle_intersections_r2_lv2_c(T r2, T a, T b, T lv2, T c, std::pair, Eigen::Matrix> &out) +{ + T x0 = - a * c / lv2; + T y0 = - b * c / lv2; + T d = r2 - c * c / lv2; + if (d < T(0)) + return 0; + T mult = sqrt(d / lv2); + out.first.x() = x0 + b * mult; + out.first.y() = y0 - a * mult; + out.second.x() = x0 - b * mult; + out.second.y() = y0 + a * mult; + return mult == T(0) ? 1 : 2; +} +template +int ray_circle_intersections(T r, T a, T b, T c, std::pair, Eigen::Matrix> &out) +{ + T lv2 = a * a + b * b; + if (lv2 < T(SCALED_EPSILON * SCALED_EPSILON)) { + //FIXME what is the correct epsilon? + // What if the line touches the circle? + return false; + } + return ray_circle_intersections_r2_lv2_c2(r * r, a, b, a * a + b * b, c, out); +} + Pointf3s convex_hull(Pointf3s points); Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); @@ -298,12 +342,12 @@ template T angle_to_0_2PI(T angle) } /// Find the center of the circle corresponding to the vector of Points as an arc. -Point circle_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20); -inline Point circle_taubin_newton(const Points& input, size_t cycles = 20) { return circle_taubin_newton(input.cbegin(), input.cend(), cycles); } +Point circle_center_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20); +inline Point circle_center_taubin_newton(const Points& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); } /// Find the center of the circle corresponding to the vector of Pointfs as an arc. -Vec2d circle_taubin_newton(const Vec2ds::const_iterator& input_start, const Vec2ds::const_iterator& input_end, size_t cycles = 20); -inline Vec2d circle_taubin_newton(const Vec2ds& input, size_t cycles = 20) { return circle_taubin_newton(input.cbegin(), input.cend(), cycles); } +Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_start, const Vec2ds::const_iterator& input_end, size_t cycles = 20); +inline Vec2d circle_center_taubin_newton(const Vec2ds& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); } void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 5082bb746..1e7ca1656 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -132,6 +132,7 @@ public: void rotate(double angle, const Point ¢er); Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } + Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; } Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } int nearest_point_index(const Points &points) const; int nearest_point_index(const PointConstPtrs &points) const; @@ -174,6 +175,12 @@ inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; } +inline Point lerp(const Point &a, const Point &b, double t) +{ + assert((t >= -EPSILON) && (t <= 1. + EPSILON)); + return ((1. - t) * a.cast() + t * b.cast()).cast(); +} + namespace int128 { // Exact orientation predicate, // returns +1: CCW, 0: collinear, -1: CW. diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index a404d230d..4371cbeee 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -103,12 +103,6 @@ enum Axis { NUM_AXES_WITH_UNKNOWN, }; -template -inline void append_to(std::vector &dst, const std::vector &src) -{ - dst.insert(dst.end(), src.begin(), src.end()); -} - template inline void append(std::vector& dest, const std::vector& src) { @@ -123,8 +117,34 @@ inline void append(std::vector& dest, std::vector&& src) { if (dest.empty()) dest = std::move(src); - else + else { + dest.reserve(dest.size() + src.size()); std::move(std::begin(src), std::end(src), std::back_inserter(dest)); + } + src.clear(); + src.shrink_to_fit(); +} + +// Append the source in reverse. +template +inline void append_reversed(std::vector& dest, const std::vector& src) +{ + if (dest.empty()) + dest = src; + else + dest.insert(dest.end(), src.rbegin(), src.rend()); +} + +// Append the source in reverse. +template +inline void append_reversed(std::vector& dest, std::vector&& src) +{ + if (dest.empty()) + dest = std::move(src); + else { + dest.reserve(dest.size() + src.size()); + std::move(std::rbegin(src), std::rend(src), std::back_inserter(dest)); + } src.clear(); src.shrink_to_fit(); } diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp index 6f2bd1c39..24e0908cc 100644 --- a/tests/libslic3r/test_geometry.cpp +++ b/tests/libslic3r/test_geometry.cpp @@ -168,21 +168,21 @@ SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") { WHEN("Circle fit is called on the entire array") { Vec2d result_center(0,0); - result_center = Geometry::circle_taubin_newton(sample); + result_center = Geometry::circle_center_taubin_newton(sample); THEN("A center point of -6,0 is returned.") { REQUIRE(is_approx(result_center, expected_center)); } } WHEN("Circle fit is called on the first four points") { Vec2d result_center(0,0); - result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4); + result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4); THEN("A center point of -6,0 is returned.") { REQUIRE(is_approx(result_center, expected_center)); } } WHEN("Circle fit is called on the middle four points") { Vec2d result_center(0,0); - result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6); + result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6); THEN("A center point of -6,0 is returned.") { REQUIRE(is_approx(result_center, expected_center)); } @@ -199,21 +199,21 @@ SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") { WHEN("Circle fit is called on the entire array") { Vec2d result_center(0,0); - result_center = Geometry::circle_taubin_newton(sample); + result_center = Geometry::circle_center_taubin_newton(sample); THEN("A center point of 3,9 is returned.") { REQUIRE(is_approx(result_center, expected_center)); } } WHEN("Circle fit is called on the first four points") { Vec2d result_center(0,0); - result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4); + result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4); THEN("A center point of 3,9 is returned.") { REQUIRE(is_approx(result_center, expected_center)); } } WHEN("Circle fit is called on the middle four points") { Vec2d result_center(0,0); - result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6); + result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6); THEN("A center point of 3,9 is returned.") { REQUIRE(is_approx(result_center, expected_center)); } @@ -230,21 +230,21 @@ SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") { WHEN("Circle fit is called on the entire array") { Point result_center(0,0); - result_center = Geometry::circle_taubin_newton(sample); + result_center = Geometry::circle_center_taubin_newton(sample); THEN("A center point of scaled 3,9 is returned.") { REQUIRE(is_approx(result_center, expected_center)); } } WHEN("Circle fit is called on the first four points") { Point result_center(0,0); - result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4); + result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4); THEN("A center point of scaled 3,9 is returned.") { REQUIRE(is_approx(result_center, expected_center)); } } WHEN("Circle fit is called on the middle four points") { Point result_center(0,0); - result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6); + result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6); THEN("A center point of scaled 3,9 is returned.") { REQUIRE(is_approx(result_center, expected_center)); }