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:
parent
517477f0dd
commit
89df9c1038
3 changed files with 383 additions and 127 deletions
|
@ -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 ¶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<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
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue