Improvement of AdaptiveFill:

1) Merging of collinear infill lines separated by a thin gap created
   by trimming with the boundary polygon.
2) Sorting of the T-joints separately to the left / right of the common
   line.
3) Trimming self intersections of the anchor lines.
4) Dropping of very short segments, not anchoring short segments.
This commit is contained in:
Vojtech Bubnik 2020-11-10 13:56:12 +01:00
parent 517477f0dd
commit 89df9c1038
3 changed files with 383 additions and 127 deletions

View file

@ -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<Line> other_hook() const {
std::optional<Line> 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<Line> 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<double>();
}
void update_left_right() {
Vec2d v1((this->closest_line.b - this->closest_line.a).cast<double>());
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<std::pair<Intersection*, double>> &intersect_line, const size_t first_idx)
static inline Intersection* get_nearest_intersection(std::vector<std::pair<Intersection*, double>>& 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<std::pair<Inter
// 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_offset)
static Line create_offset_line(Line offset_line, const Intersection &intersection, const double scaled_offset)
{
Vec2d dir = line_to_offset.vector().cast<double>().normalized();
Vec2d dir = intersection.closest_line.vector().cast<double>().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<double>()) < 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<Line> other_hook = intersection.other_hook();
Vec2d hook_vector_norm = intersection.closest_line.vector().cast<double>().normalized();
Vector hook_vector = (hook_length * hook_vector_norm).cast<coord_t>();
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<std::pair<rtree_segment_t, size_t>> 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<std::pair<rtree_segment_t, size_t>> &hook_intersections) {
auto max_hook_length = [hook_length](
const Vec2d &pt, const Vec2d &dir,
const std::vector<std::pair<rtree_segment_t, size_t>> &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<double>() - 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>();
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<coord_t>();
}
}
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<std::pair<rtree_segment_t, size_t>> 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<Polyline*, bool> {
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<double>().squaredNorm();
double dist2_back = (other.points.back() - pt).cast<double>().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<double>().squaredNorm(), (other.points.back() - pt_other).cast<double>().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<double>();
Vec2d v2 = (other.points.back() - other.points.front()).cast<double>();
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<Polyline*>(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<Intersection> intersections;
{
// Keeping the vector of closest points outside the loop, so the vector does not need to be reallocated.
std::vector<std::pair<rtree_segment_t, size_t>> 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<size_t> 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<size_t> 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<double>())) > 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<size_t> 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<double>()));
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<double>().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<std::pair<rtree_segment_t, size_t>> 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<double>().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<double>()); }
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<LineEnd, LineEndAccessor> 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<std::pair<const LineEnd*, double>> hits = closest_end_point_lookup.find_all(line_start.point());
for (const std::pair<const LineEnd*, double> &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 &params,
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<coord_t>::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<Line> lines;
lines.reserve(num_lines);
@ -996,25 +1229,21 @@ void Filler::_fill_surface_single(
if (line.a.x() != std::numeric_limits<coord_t>::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
{

View file

@ -24,7 +24,7 @@ namespace line_alg {
template<class L, class T, int N>
double distance_to_squared(const L &line, const Vec<N, T> &point)
{
const Vec<N, double> v = line.vector().template cast<double>();
const Vec<N, double> v = (line.b - line.a).template cast<double>();
const Vec<N, double> va = (point - line.a).template cast<double>();
const double l2 = v.squaredNorm(); // avoid a sqrt
if (l2 == 0.0)

View file

@ -298,6 +298,33 @@ public:
std::make_pair(nullptr, std::numeric_limits<double>::max());
}
// Returns all pairs of values and squared distances.
std::vector<std::pair<const ValueType*, double>> 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<std::pair<const ValueType*, double>> 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<double>().squaredNorm();
if (d2 <= r2)
out.emplace_back(&value, d2);
}
}
}
}
return out;
}
private:
typedef typename std::unordered_multimap<Vec2crd, ValueType, PointHash> map_type;
PointAccessor m_point_accessor;