diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index b8b18e3a68..18df5b277f 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -574,14 +574,49 @@ struct Intersection Line intersect_line; // Indicate if intersect_point is the first or the last point of intersect_pl bool front; + // Signum of intersect_line_dir.cross(closest_line.dir()): + bool left; // Indication if this intersection has been proceed bool used = false; bool fresh() const throw() { return ! used && ! intersect_pl->empty(); } + + std::optional other_hook() const { + std::optional out; + const Points &pts = intersect_pl->points; + if (pts.size() >= 3) + out = this->front ? Line(pts[1], pts[2]) : Line(pts[pts.size() - 2], pts[pts.size() - 3]); + return out; + } + + bool other_hook_intersects(const Line &l, Point &pt) { + std::optional h = other_hook(); + return h && h->intersection(l, &pt); + } + bool other_hook_intersects(const Line &l) { Point pt; return this->other_hook_intersects(l, pt); } + + // Direction to intersect_point. + Vec2d intersect_line_dir() const throw() { + return (this->intersect_point == intersect_line.a ? intersect_line.b - intersect_line.a : intersect_line.a - intersect_line.b).cast(); + } + + void update_left_right() { + Vec2d v1((this->closest_line.b - this->closest_line.a).cast()); + Vec2d v2(this->intersect_line_dir()); +#ifndef NDEBUG + { + Vec2d v1n = v1.normalized(); + Vec2d v2n = v2.normalized(); + double c = cross2(v1n, v2n); + assert(std::abs(c) > sin(M_PI / 12.)); + } +#endif // NDEBUG + this->left = cross2(v1, v2) > 0.; + } }; -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; @@ -603,23 +638,16 @@ static inline Intersection *get_nearest_intersection(std::vector().normalized(); + Vec2d dir = intersection.closest_line.vector().cast().normalized(); // 50% overlap of the extrusion lines to achieve strong bonding. - Vec2d offset_vector = Vec2d(- dir.y(), dir.x()) * scaled_offset; - const Point &furthest_point = (intersection.intersect_point == intersection.intersect_line.a ? intersection.intersect_line.b : intersection.intersect_line.a); - - // Move inside. - if (offset_vector.dot((furthest_point - intersection.intersect_point).cast()) < 0.) - offset_vector *= -1.; - - Line offset_line = line_to_offset; + Vec2d offset_vector = Vec2d(- dir.y(), dir.x()) * (intersection.left ? scaled_offset : - scaled_offset); offset_line.translate(offset_vector.x(), offset_vector.y()); // Extend the line by a small value to guarantee a collision with adjacent lines offset_line.extend(coord_t(scaled_offset * 1.16)); // / cos(PI/6) return offset_line; -}; +} namespace bg = boost::geometry; namespace bgm = boost::geometry::model; @@ -650,6 +678,8 @@ static void add_hook(const Intersection &intersection, const double scaled_offse &hook_start); assert(intersection_found); + std::optional other_hook = intersection.other_hook(); + 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); @@ -658,9 +688,11 @@ static void add_hook(const Intersection &intersection, const double scaled_offse std::vector> hook_intersections; rtree.query(bgi::intersects(mk_rtree_seg(hook_forward)) && bgi::satisfies(filter_itself), std::back_inserter(hook_intersections)); + Point self_intersection_point; + bool self_intersection = other_hook && other_hook->intersection(hook_forward, &self_intersection_point); Point hook_end; - if (hook_intersections.empty()) { + if (hook_intersections.empty() && ! self_intersection) { // The hook is not limited by another infill line. Extrude it in its full length. hook_end = hook_forward.b; } else { @@ -668,7 +700,10 @@ static void add_hook(const Intersection &intersection, const double scaled_offse // 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) { + auto max_hook_length = [hook_length]( + const Vec2d &pt, const Vec2d &dir, + const std::vector> &hook_intersections, + bool self_intersection, const Point &self_intersection_point) { // 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) { @@ -690,33 +725,39 @@ static void add_hook(const Intersection &intersection, const double scaled_offse } else update_max_length(cross2(pt2 - pt, dir2) / denom); } + if (self_intersection) { + double t = (self_intersection_point.cast() - pt).norm(); + max_length = std::min(max_length, t); + } 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); + double hook_forward_max_length = max_hook_length(hook_startf, hook_vector_norm, hook_intersections, self_intersection, self_intersection_point); hook_intersections.clear(); 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)); + self_intersection = other_hook && other_hook->intersection(hook_backward, &self_intersection_point); - if (hook_intersections.empty()) { - // The hook in the other direction is not limited by another infill line. Extrude it in its full length. + if (hook_intersections.empty() && ! self_intersection) { + // 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 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); + double hook_backward_max_length = max_hook_length(hook_startf, - hook_vector_norm, hook_intersections, self_intersection, self_intersection_point); 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(); } } + Points &pl = intersection.intersect_pl->points; if (intersection.front) { - intersection.intersect_pl->points.front() = hook_start; - intersection.intersect_pl->points.emplace(intersection.intersect_pl->points.begin(), hook_end); + pl.front() = hook_start; + pl.emplace(pl.begin(), hook_end); } else { - intersection.intersect_pl->points.back() = hook_start; - intersection.intersect_pl->points.emplace_back(hook_end); + pl.back() = hook_start; + pl.emplace_back(hook_end); } } @@ -724,49 +765,146 @@ 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++)); + // Keeping the vector of closest points outside the loop, so the vector does not need to be reallocated. + std::vector> closest; + { + // Insert infill lines into rtree, merge close collinear segments split by the infill boundary. + double r2_close = Slic3r::sqr(1200.); + for (Polyline &poly : lines) { + assert(poly.points.size() == 2); + if (&poly != lines.data()) { + // Join collinear segments separated by a tiny gap. These gaps were likely created by clipping the infill lines with a concave dent in an infill boundary. + auto collinear_segment = [&rtree, &closest, &lines, r2_close](const Point &pt, const Point &pt_other) -> std::pair { + closest.clear(); + rtree.query(bgi::nearest(mk_rtree_point(pt), 1), std::back_inserter(closest)); + Polyline &other = lines[closest.front().second]; + double dist2_front = (other.points.front() - pt).cast().squaredNorm(); + double dist2_back = (other.points.back() - pt).cast().squaredNorm(); + double dist2_min = std::min(dist2_front, dist2_back); + if (dist2_min < r2_close) { + // Don't connect the segments in an opposite direction. + double dist2_min_other = std::min((other.points.front() - pt_other).cast().squaredNorm(), (other.points.back() - pt_other).cast().squaredNorm()); + if (dist2_min_other > dist2_min) { + // End points of the two lines are very close, they should have been merged together if they are collinear. + Vec2d v1 = (pt_other - pt).cast(); + Vec2d v2 = (other.points.back() - other.points.front()).cast(); + Vec2d v1n = v1.normalized(); + Vec2d v2n = v2.normalized(); + // The vectors must not be collinear. + double d = v1n.dot(v2n); + if (std::abs(d) > 0.99f) { + // Lines are collinear, merge them. + rtree.remove(closest.front()); + return std::make_pair(&other, dist2_min == dist2_front); + } + } + } + return std::make_pair(static_cast(nullptr), false); + }; + auto collinear_front = collinear_segment(poly.points.front(), poly.points.back()); + if (collinear_front.first) { + Polyline &other = *collinear_front.first; + poly.points.front() = collinear_front.second ? other.points.back() : other.points.front(); + other.points.clear(); + } + auto collinear_back = collinear_segment(poly.points.back(), poly.points.front()); + if (collinear_back.first) { + Polyline &other = *collinear_front.first; + poly.points.back() = collinear_front.second ? other.points.back() : other.points.front(); + other.points.clear(); + } + } + rtree.insert(std::make_pair(mk_rtree_seg(poly.points.front(), poly.points.back()), poly_idx++)); + } } const float scaled_offset = float(scale_(spacing) * 0.7); // 30% overlap std::vector intersections; { - // Keeping the vector of closest points outside the loop, so the vector does not need to be reallocated. - std::vector> closest; // Minimum lenght of an infill line to anchor. Very short lines cannot be trimmed from both sides, // it does not help to anchor extremely short infill lines, it consumes too much plastic while not adding // to the object rigidity. - const double line_len_threshold = scaled_offset * 4.; - // Minimum length of an infill line to be trimmed from both sides. - assert(line_len_threshold > scaled_offset * (2. / cos(PI / 6.)) + SCALED_EPSILON); - for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) { - 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. - //FIXME we should rather remove such short infill lines earlier! - if (line.length() < line_len_threshold) + const double line_len_threshold_drop_both_sides = scaled_offset * (2. / cos(PI / 6.) + 0.5) + SCALED_EPSILON; + const double line_len_threshold_anchor_both_sides = line_len_threshold_drop_both_sides + scaled_offset; + const double line_len_threshold_drop_single_side = scaled_offset * (1. / cos(PI / 6.) + 1.5) + SCALED_EPSILON; + const double line_len_threshold_anchor_single_side = line_len_threshold_drop_single_side + scaled_offset; + for (size_t line_idx = 0; line_idx < lines.size(); ++ line_idx) { + Polyline &line = lines[line_idx]; + if (line.points.empty()) continue; 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(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 }); + std::optional tjoint_front, tjoint_back; + { + auto has_tjoint = [&closest, line_idx, &rtree, &lines](const Point &pt) { + auto filter_itself = [line_idx](const auto &item) { return item.second != line_idx; }; + closest.clear(); + rtree.query(bgi::nearest(mk_rtree_point(pt), 1) && bgi::satisfies(filter_itself), std::back_inserter(closest)); + const Polyline &pl = lines[closest.front().second]; + std::optional out; + if (pl.points.empty()) { + // The closest infill line was already dropped as it was too short. + // Such an infill line should not make a T-joint anyways. +#if 0 // #ifndef NDEBUG + const auto &seg = closest.front().first; + struct Linef { Vec2d a; Vec2d b; }; + Linef l { { bg::get<0, 0>(seg), bg::get<0, 1>(seg) }, { bg::get<1, 0>(seg), bg::get<1, 1>(seg) } }; + assert(line_alg::distance_to_squared(l, Vec2d(pt.cast())) > 1000 * 1000); +#endif // NDEBUG + } else if (((Line)pl).distance_to_squared(pt) <= 1000 * 1000) + out = closest.front().second; + return out; + }; + tjoint_front = has_tjoint(front_point); + tjoint_back = has_tjoint(back_point); + } - // Find the nearest line from the end point of the line - closest.clear(); - 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 }); + int num_tjoints = int(tjoint_front.has_value()) + int(tjoint_back.has_value()); + if (num_tjoints > 0) { + double line_len = line.length(); + bool drop = false; + bool anchor = false; + if (num_tjoints == 1) { + // Connected to perimeters on a single side only, connected to another infill line on the other side. + drop = line_len < line_len_threshold_drop_single_side; + anchor = line_len > line_len_threshold_anchor_single_side; + } else { + // Not connected to perimeters at all, connected to two infill lines. + assert(num_tjoints == 2); + drop = line_len < line_len_threshold_drop_both_sides; + anchor = line_len > line_len_threshold_anchor_both_sides; + } + if (drop) { + // Drop a very short line if connected to another infill line. + // 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. + line.points.clear(); + } else if (anchor) { + if (tjoint_front) { + // T-joint of line's front point with the 'closest' line. + intersections.push_back({ tjoint_front.value(), (Line)lines[tjoint_front.value()], front_point, line_idx, &line, (Line)line, true }); + intersections.back().update_left_right(); + } + if (tjoint_back) { + // T-joint of line's back point with the 'closest' line. + intersections.push_back({ tjoint_back.value(), (Line)lines[tjoint_back.value()], back_point, line_idx, &line, (Line)line, false }); + intersections.back().update_left_right(); + } + } + } + } + // Remove those intersections, that point to a dropped line. + for (auto it = intersections.begin(); it != intersections.end(); ) { + assert(! lines[it->intersect_line_idx].points.empty()); + if (lines[it->closest_line_idx].points.empty()) { + *it = intersections.back(); + intersections.pop_back(); + } else + ++ it; } } @@ -781,8 +919,13 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b } #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ + // Sort lexicographically by closest_line_idx and left/right orientation. std::sort(intersections.begin(), intersections.end(), - [](const Intersection &i1, const Intersection &i2) { return i1.closest_line_idx < i2.closest_line_idx; }); + [](const Intersection &i1, const Intersection &i2) { + return (i1.closest_line_idx == i2.closest_line_idx) ? + int(i1.left) < int(i2.left) : + i1.closest_line_idx < i2.closest_line_idx; + }); std::vector merged_with(lines.size()); std::iota(merged_with.begin(), merged_with.end(), 0); @@ -833,7 +976,10 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b // 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. { size_t max_idx = min_idx; - for (; max_idx < intersections.size() && intersections[min_idx].closest_line_idx == intersections[max_idx].closest_line_idx; ++ max_idx) + for (; max_idx < intersections.size() && + intersections[min_idx].closest_line_idx == intersections[max_idx].closest_line_idx && + intersections[min_idx].left == intersections[max_idx].left; + ++ max_idx) intersect_line.emplace_back(&intersections[max_idx], line_dir.dot(intersections[max_idx].intersect_point.cast())); min_idx = max_idx; } @@ -873,85 +1019,105 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b if (first_idx + 1 < intersect_line.size()) update_merged_polyline(*intersect_line[first_idx + 1].first); Intersection &nearest_i = *get_nearest_intersection(intersect_line, first_idx); - + assert(first_i.closest_line_idx == nearest_i.closest_line_idx); + assert(first_i.intersect_line_idx != nearest_i.intersect_line_idx); + assert(first_i.intersect_line_idx != first_i.closest_line_idx); + assert(nearest_i.intersect_line_idx != first_i.closest_line_idx); // A line between two intersections points Line offset_line = create_offset_line(Line(first_i.intersect_point, nearest_i.intersect_point), first_i, scaled_offset); // 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)) { - bool connected = false; - 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. - // Verify that no other infill line intersects this anchor line. - std::vector> hook_intersections; - rtree.query( - bgi::intersects(mk_rtree_seg(first_i_point, nearest_i_point)) && - bgi::satisfies([&first_i, &nearest_i](const auto &item) { return item.second != first_i.intersect_line_idx && item.second != nearest_i.intersect_line_idx; }), - std::back_inserter(hook_intersections)); - if (hook_intersections.empty()) { + bool could_connect = false; + if (nearest_i.fresh()) { + could_connect = first_i.intersect_line.intersection(offset_line, &first_i_point) && + nearest_i.intersect_line.intersection(offset_line, &nearest_i_point); + assert(could_connect); + } + Points &first_points = first_i.intersect_pl->points; + Points &second_points = nearest_i.intersect_pl->points; + could_connect &= (nearest_i_point - first_i_point).cast().squaredNorm() <= Slic3r::sqr(3. * hook_length); + if (could_connect) { + // Both intersections are so close that their polylines can be connected. + // Verify that no other infill line intersects this anchor line. + closest.clear(); + rtree.query( + bgi::intersects(mk_rtree_seg(first_i_point, nearest_i_point)) && + bgi::satisfies([&first_i, &nearest_i](const auto &item) { return item.second != first_i.intersect_line_idx && item.second != nearest_i.intersect_line_idx; }), + std::back_inserter(closest)); + could_connect = closest.empty(); +#if 0 + // Avoid self intersections. Maybe it is better to trim the self intersection after the connection? + if (could_connect && first_i.intersect_pl != nearest_i.intersect_pl) { + Line l(first_i_point, nearest_i_point); + could_connect = ! first_i.other_hook_intersects(l) && ! nearest_i.other_hook_intersects(l); + } +#endif + } + bool connected = false; + if (could_connect) { #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT - export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-connecting-pre-%d-%d.svg", iRun, iStep), { first_i.intersect_point, nearest_i.intersect_point }); -#endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT - // No other infill line intersects this anchor line. Extrude it as a whole. - if (first_i.intersect_pl == nearest_i.intersect_pl) { - // Both intersections are on the same polyline, that means a loop is being closed. - assert(first_i.front != nearest_i.front); - 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 &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 < nearest_i.intersect_pl) { - second_points.clear(); - merged_with[nearest_i.intersect_pl - lines.data()] = first_i.intersect_pl - lines.data(); - } else { - second_points = std::move(first_points); - first_points.clear(); - merged_with[first_i.intersect_pl - lines.data()] = nearest_i.intersect_pl - lines.data(); - } - } - nearest_i.used = true; - connected = true; -#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT - export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-connecting-post-%d-%d.svg", iRun, iStep), { first_i.intersect_point, nearest_i.intersect_point }); + export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-connecting-pre-%d-%d.svg", iRun, iStep), { first_i.intersect_point, nearest_i.intersect_point }); #endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + // No other infill line intersects this anchor line. Extrude it as a whole. + if (first_i.intersect_pl == nearest_i.intersect_pl) { + // Both intersections are on the same polyline, that means a loop is being closed. + assert(first_i.front != nearest_i.front); + if (! first_i.front) + std::swap(first_i_point, nearest_i_point); + first_points.front() = first_i_point; + first_points.back() = nearest_i_point; + //FIXME trim the end of a closed loop a bit? + first_points.emplace(first_points.begin(), nearest_i_point); + } else { + // Both intersections are on different polylines + Line l(first_i_point, nearest_i_point); + Point pt_start, pt_end; + bool trim_start = first_i .intersect_pl->points.size() == 3 && first_i .other_hook_intersects(l, pt_start); + bool trim_end = nearest_i.intersect_pl->points.size() == 3 && nearest_i.other_hook_intersects(l, pt_end); + first_points.reserve(first_points.size() + second_points.size()); + if (first_i.front) + std::reverse(first_points.begin(), first_points.end()); + if (trim_start) + first_points.front() = pt_start; + 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()); + if (trim_end) + first_points.back() = pt_end; + // Keep the polyline at the lower index slot. + if (first_i.intersect_pl < nearest_i.intersect_pl) { + second_points.clear(); + merged_with[nearest_i.intersect_pl - lines.data()] = first_i.intersect_pl - lines.data(); + } else { + second_points = std::move(first_points); + first_points.clear(); + merged_with[first_i.intersect_pl - lines.data()] = nearest_i.intersect_pl - lines.data(); } } - if (! connected) { - // Try to connect left or right. If not enough space for hook_length, take the longer side. + nearest_i.used = true; + connected = true; #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT - export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-add_hook-pre-%d-%d.svg", iRun, iStep), { first_i.intersect_point }); + export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-connecting-post-%d-%d.svg", iRun, iStep), { first_i.intersect_point, nearest_i.intersect_point }); #endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT - add_hook(first_i, scaled_offset, hook_length, rtree); -#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT - export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-add_hook-pre-%d-%d.svg", iRun, iStep), { first_i.intersect_point }); -#endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT - } -#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT - ++iStep; -#endif ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT - first_i.used = true; - } else { - // The first & last point should always be found. - assert(false); } + if (! connected) { + // Try to connect left or right. If not enough space for hook_length, take the longer side. +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-add_hook-pre-%d-%d.svg", iRun, iStep), { first_i.intersect_point }); +#endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + add_hook(first_i, scaled_offset, hook_length, rtree); +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + export_infill_lines_to_svg(boundary, lines, debug_out_path("FillAdaptive-add_hook-post-%d-%d.svg", iRun, iStep), { first_i.intersect_point }); +#endif // ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + } +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + ++ iStep; +#endif ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + first_i.used = true; } } @@ -962,8 +1128,58 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b return polylines_out; } +//coord_t get_hook_length(const double spacing) { return coord_t(scale_(spacing)) * 2; } coord_t get_hook_length(const double spacing) { return coord_t(scale_(spacing)) * 5; } +#ifndef NDEBUG +bool has_no_collinear_lines(const Polylines &polylines) +{ + // Create line end point lookup. + struct LineEnd { + LineEnd(const Polyline *line, bool start) : line(line), start(start) {} + const Polyline *line; + // Is it the start or end point? + bool start; + const Point& point() const { return start ? line->points.front() : line->points.back(); } + const Point& other_point() const { return start ? line->points.back() : line->points.front(); } + LineEnd other_end() const { return LineEnd(line, !start); } + Vec2d vec() const { return Vec2d((this->other_point() - this->point()).cast()); } + bool operator==(const LineEnd &rhs) const { return this->line == rhs.line && this->start == rhs.start; } + }; + struct LineEndAccessor { + const Point* operator()(const LineEnd &pt) const { return &pt.point(); } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + ClosestPointLookupType closest_end_point_lookup(1001. * sqrt(2.)); + for (const Polyline& pl : polylines) { +// assert(pl.points.size() == 2); + auto line_start = LineEnd(&pl, true); + auto line_end = LineEnd(&pl, false); + + auto assert_not_collinear = [&closest_end_point_lookup](const LineEnd &line_start) { + std::vector> hits = closest_end_point_lookup.find_all(line_start.point()); + for (const std::pair &hit : hits) + if ((line_start.point() - hit.first->point()).cwiseAbs().maxCoeff() <= 1000) { + // End points of the two lines are very close, they should have been merged together if they are collinear. + Vec2d v1 = line_start.vec(); + Vec2d v2 = hit.first->vec(); + Vec2d v1n = v1.normalized(); + Vec2d v2n = v2.normalized(); + // The vectors must not be collinear. + assert(std::abs(v1n.dot(v2n)) < cos(M_PI / 12.)); + } + }; + assert_not_collinear(line_start); + assert_not_collinear(line_end); + + closest_end_point_lookup.insert(line_start); + closest_end_point_lookup.insert(line_end); + } + + return true; +} +#endif + void Filler::_fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, @@ -987,6 +1203,23 @@ void Filler::_fill_surface_single( generate_infill_lines_recursive(context, adapt_fill_octree->root_cube, 0, int(adapt_fill_octree->cubes_properties.size()) - 1); num_lines += context.output_lines.size() + context.temp_lines.size(); } + +#if 0 + // Collect the lines, trim them by the expolygon. + all_polylines.reserve(num_lines); + auto boundary = to_polygons(expolygon); + for (auto &context : contexts) { + Polylines lines; + lines.reserve(context.output_lines.size() + context.temp_lines.size()); + std::transform(context.output_lines.begin(), context.output_lines.end(), std::back_inserter(lines), [](const Line& l) { return Polyline{ l.a, l.b }; }); + for (const Line &l : context.temp_lines) + if (l.a.x() != std::numeric_limits::max()) + lines.push_back({ l.a, l.b }); + // Crop all polylines + append(all_polylines, intersection_pl(std::move(lines), boundary)); + } +// assert(has_no_collinear_lines(all_polylines)); +#else // Collect the lines. std::vector lines; lines.reserve(num_lines); @@ -996,25 +1229,21 @@ void Filler::_fill_surface_single( if (line.a.x() != std::numeric_limits::max()) lines.emplace_back(line); } -#if 0 - // Chain touching line segments, convert lines to polylines. - //all_polylines = chain_lines(lines, 300.); // SCALED_EPSILON is 100 and it is not enough -#else // Convert lines to polylines. all_polylines.reserve(lines.size()); std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; }); + // Crop all polylines + all_polylines = intersection_pl(std::move(all_polylines), to_polygons(expolygon)); #endif } - // Crop all polylines - all_polylines = intersection_pl(std::move(all_polylines), to_polygons(expolygon)); - // After intersection_pl some polylines with only one line are split into more lines for (Polyline &polyline : all_polylines) { //FIXME assert that all the points are collinear and in between the start and end point. if (polyline.points.size() > 2) polyline.points.erase(polyline.points.begin() + 1, polyline.points.end() - 1); } +// assert(has_no_collinear_lines(all_polylines)); #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT { @@ -1024,7 +1253,7 @@ void Filler::_fill_surface_single( #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ coord_t hook_length = get_hook_length(this->spacing); - Polylines all_polylines_with_hooks = connect_lines_using_hooks(std::move(all_polylines), expolygon, this->spacing, hook_length); + Polylines all_polylines_with_hooks = all_polylines.size() > 1 ? connect_lines_using_hooks(std::move(all_polylines), expolygon, this->spacing, hook_length) : std::move(all_polylines); #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT { diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index aeb1851600..46f1a0442f 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -24,7 +24,7 @@ namespace line_alg { template double distance_to_squared(const L &line, const Vec &point) { - const Vec v = line.vector().template cast(); + const Vec v = (line.b - line.a).template cast(); const Vec va = (point - line.a).template cast(); const double l2 = v.squaredNorm(); // avoid a sqrt if (l2 == 0.0) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 1e7ca16566..883933cd73 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -298,6 +298,33 @@ public: std::make_pair(nullptr, std::numeric_limits::max()); } + // Returns all pairs of values and squared distances. + std::vector> find_all(const Vec2crd &pt) { + // Iterate over 4 closest grid cells around pt, + // Round pt to a closest grid_cell corner. + Vec2crd grid_corner((pt(0)+(m_grid_resolution>>1))>>m_grid_log2, (pt(1)+(m_grid_resolution>>1))>>m_grid_log2); + // For four neighbors of grid_corner: + std::vector> out; + const double r2 = double(m_search_radius) * m_search_radius; + for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { + for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { + // Range of fragment starts around grid_corner, close to pt. + auto range = m_map.equal_range(Vec2crd(grid_corner(0) + neighbor_x, grid_corner(1) + neighbor_y)); + // Find the map entry closest to pt. + for (auto it = range.first; it != range.second; ++it) { + const ValueType &value = it->second; + const Vec2crd *pt2 = m_point_accessor(value); + if (pt2 != nullptr) { + const double d2 = (pt - *pt2).cast().squaredNorm(); + if (d2 <= r2) + out.emplace_back(&value, d2); + } + } + } + } + return out; + } + private: typedef typename std::unordered_multimap map_type; PointAccessor m_point_accessor;