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)); }