1) Implemented anchoring of infill lines to perimeters with length

limited anchors, while before a full perimeter segment was always
   taken if possible.
2) Adapted the line infills (grid, stars, triangles, cubic) to 1).
   This also solves a long standing issue of these infills producing
   anchors for each sweep direction independently, thus possibly
   overlapping and overextruding, which was quite detrimental
   in narrow areas.
3) Refactored cubic adaptive infill anchroing algorithm
   for performance and clarity.
This commit is contained in:
Vojtech Bubnik 2020-11-05 17:32:40 +01:00
parent 414fdaefc5
commit 239d588c5d
12 changed files with 1200 additions and 626 deletions

View File

@ -55,6 +55,24 @@ void EdgeGrid::Grid::create(const Polygons &polygons, coord_t resolution)
create_from_m_contours(resolution); create_from_m_contours(resolution);
} }
void EdgeGrid::Grid::create(const std::vector<const Polygon*> &polygons, coord_t resolution)
{
// Count the contours.
size_t ncontours = 0;
for (size_t j = 0; j < polygons.size(); ++ j)
if (! polygons[j]->points.empty())
++ ncontours;
// Collect the contours.
m_contours.assign(ncontours, nullptr);
ncontours = 0;
for (size_t j = 0; j < polygons.size(); ++ j)
if (! polygons[j]->points.empty())
m_contours[ncontours ++] = &polygons[j]->points;
create_from_m_contours(resolution);
}
void EdgeGrid::Grid::create(const std::vector<Points> &polygons, coord_t resolution) void EdgeGrid::Grid::create(const std::vector<Points> &polygons, coord_t resolution)
{ {
// Count the contours. // Count the contours.
@ -1150,7 +1168,7 @@ EdgeGrid::Grid::ClosestPointResult EdgeGrid::Grid::closest_point(const Point &pt
if (result.contour_idx != size_t(-1) && d_min <= double(search_radius)) { if (result.contour_idx != size_t(-1) && d_min <= double(search_radius)) {
result.distance = d_min * sign_min; result.distance = d_min * sign_min;
result.t /= l2_seg_min; result.t /= l2_seg_min;
assert(result.t >= 0. && result.t < 1.); assert(result.t >= 0. && result.t <= 1.);
#ifndef NDEBUG #ifndef NDEBUG
{ {
const Slic3r::Points &pts = *m_contours[result.contour_idx]; const Slic3r::Points &pts = *m_contours[result.contour_idx];

View File

@ -21,6 +21,7 @@ public:
void set_bbox(const BoundingBox &bbox) { m_bbox = bbox; } void set_bbox(const BoundingBox &bbox) { m_bbox = bbox; }
void create(const Polygons &polygons, coord_t resolution); void create(const Polygons &polygons, coord_t resolution);
void create(const std::vector<const Polygon*> &polygons, coord_t resolution);
void create(const std::vector<Points> &polygons, coord_t resolution); void create(const std::vector<Points> &polygons, coord_t resolution);
void create(const ExPolygon &expoly, coord_t resolution); void create(const ExPolygon &expoly, coord_t resolution);
void create(const ExPolygons &expolygons, coord_t resolution); void create(const ExPolygons &expolygons, coord_t resolution);

View File

@ -553,21 +553,15 @@ static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines
} }
#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
static Matrix2d rotation_matrix_from_vector(const Point &vector) // Representing a T-joint (in general case) between two infill lines
{ // (between one end point of intersect_pl/intersect_line and
Matrix2d rotation;
rotation.block<1, 2>(0, 0) = vector.cast<double>().normalized();
rotation(1, 0) = -rotation(0, 1);
rotation(1, 1) = rotation(0, 0);
return rotation;
}
struct Intersection struct Intersection
{ {
// Index of the closest line to intersect_line // Index of the closest line to intersect_line
size_t closest_line_idx; size_t closest_line_idx;
// Copy of closest line to intersect_point, used for storing original line in an unchanged state // Copy of closest line to intersect_point, used for storing original line in an unchanged state
Line closest_line; Line closest_line;
// Point for which is computed closest line (closest_line) // Point for which is computed closest line (closest_line)
Point intersect_point; Point intersect_point;
// Index of the polyline from which is computed closest_line // Index of the polyline from which is computed closest_line
@ -577,54 +571,53 @@ struct Intersection
// The line for which is computed closest line from intersect_point to closest_line // The line for which is computed closest line from intersect_point to closest_line
Line intersect_line; Line intersect_line;
// Indicate if intersect_point is the first or the last point of intersect_pl // Indicate if intersect_point is the first or the last point of intersect_pl
bool forward; bool front;
// Indication if this intersection has been proceed // Indication if this intersection has been proceed
bool used = false; bool used = false;
Intersection(const size_t closest_line_idx, bool fresh() const throw() { return ! used && ! intersect_pl->empty(); }
const Line &closest_line,
const Point &intersect_point,
size_t intersect_pl_idx,
Polyline *intersect_pl,
const Line &intersect_line,
bool forward)
: closest_line_idx(closest_line_idx)
, closest_line(closest_line)
, intersect_point(intersect_point)
, intersect_pl_idx(intersect_pl_idx)
, intersect_pl(intersect_pl)
, intersect_line(intersect_line)
, forward(forward)
{}
}; };
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); assert(intersect_line.size() >= 2);
bool take_next = false;
if (first_idx == 0) if (first_idx == 0)
return &intersect_line[first_idx + 1].first; take_next = true;
else if (first_idx == (intersect_line.size() - 1)) else if (first_idx + 1 == intersect_line.size())
return &intersect_line[first_idx - 1].first; take_next = false;
else if ((intersect_line[first_idx].second - intersect_line[first_idx - 1].second) < (intersect_line[first_idx + 1].second - intersect_line[first_idx].second)) else {
return &intersect_line[first_idx - 1].first; // Has both prev and next.
else const std::pair<Intersection*, double> &ithis = intersect_line[first_idx];
return &intersect_line[first_idx + 1].first; const std::pair<Intersection*, double> &iprev = intersect_line[first_idx - 1];
const std::pair<Intersection*, double> &inext = intersect_line[first_idx + 1];
take_next = iprev.first->fresh() && inext.first->fresh() ?
inext.second - ithis.second < ithis.second - iprev.second :
inext.first->fresh();
}
return intersect_line[take_next ? first_idx + 1 : first_idx - 1].first;
} }
// Create a line based on line_to_offset translated it in the direction of the intersection line (intersection.intersect_line) // Create a line representing the anchor aka hook extrusion based on line_to_offset
// translated in the direction of the intersection line (intersection.intersect_line).
static Line create_offset_line(const Line &line_to_offset, const Intersection &intersection, const double scaled_spacing) static Line create_offset_line(const Line &line_to_offset, const Intersection &intersection, const double scaled_spacing)
{ {
Matrix2d rotation = rotation_matrix_from_vector(line_to_offset.vector()); Vec2d dir = line_to_offset.vector().cast<double>().normalized();
Vec2d offset_vector = ((scaled_spacing / 2.) * line_to_offset.normal().cast<double>().normalized()); // 50% overlap of the extrusion lines to achieve strong bonding.
Vec2d offset_line_point = line_to_offset.a.cast<double>(); Vec2d offset_vector = Vec2d(- dir.y(), dir.x()) * (scaled_spacing / 2.);
Vec2d furthest_point = (intersection.intersect_point == intersection.intersect_line.a ? intersection.intersect_line.b : intersection.intersect_line.a).cast<double>(); const Point &furthest_point = (intersection.intersect_point == intersection.intersect_line.a ? intersection.intersect_line.b : intersection.intersect_line.a);
if ((rotation * furthest_point).y() >= (rotation * offset_line_point).y()) offset_vector *= -1; // Move inside.
if (offset_vector.dot((furthest_point - intersection.intersect_point).cast<double>()) < 0.)
offset_vector *= -1.;
Line offset_line = line_to_offset; Line offset_line = line_to_offset;
offset_line.translate(offset_vector.x(), offset_vector.y()); offset_line.translate(offset_vector.x(), offset_vector.y());
// Extend the line by small value to guarantee a collision with adjacent lines // Extend the line by a small value to guarantee a collision with adjacent lines
offset_line.extend(coord_t(scale_(1.))); offset_line.extend(coord_t(scale_(1.)));
//FIXME scaled_spacing * tan(PI/6)
// offset_line.extend(coord_t(scaled_spacing * 0.577));
return offset_line; return offset_line;
}; };
@ -637,26 +630,29 @@ using rtree_point_t = bgm::point<float, 2, boost::geometry::cs::cartesian>;
using rtree_segment_t = bgm::segment<rtree_point_t>; using rtree_segment_t = bgm::segment<rtree_point_t>;
using rtree_t = bgi::rtree<std::pair<rtree_segment_t, size_t>, bgi::rstar<16, 4>>; using rtree_t = bgi::rtree<std::pair<rtree_segment_t, size_t>, bgi::rstar<16, 4>>;
static inline rtree_point_t mk_rtree_point(const Point &pt) {
return rtree_point_t(float(pt.x()), float(pt.y()));
}
static inline rtree_segment_t mk_rtree_seg(const Point &a, const Point &b) { static inline rtree_segment_t mk_rtree_seg(const Point &a, const Point &b) {
return { rtree_point_t(float(a.x()), float(a.y())), rtree_point_t(float(b.x()), float(b.y())) }; return { mk_rtree_point(a), mk_rtree_point(b) };
} }
static inline rtree_segment_t mk_rtree_seg(const Line &l) { static inline rtree_segment_t mk_rtree_seg(const Line &l) {
return mk_rtree_seg(l.a, l.b); return mk_rtree_seg(l.a, l.b);
} }
// Create a hook based on hook_line and append it to the begin or end of the polyline in the intersection // Create a hook based on hook_line and append it to the begin or end of the polyline in the intersection
static void add_hook(const Intersection &intersection, const Line &hook_line, const double scaled_spacing, const int hook_length, const rtree_t &rtree) static void add_hook(const Intersection &intersection, const double scaled_spacing, const int hook_length, const rtree_t &rtree)
{ {
Vec2d hook_vector_norm = hook_line.vector().cast<double>().normalized(); // Trim the hook start by the infill line it will connect to.
Vector hook_vector = (hook_length * hook_vector_norm).cast<coord_t>(); Point hook_start;
Line hook_line_offset = create_offset_line(hook_line, intersection, scaled_spacing); bool intersection_found = intersection.intersect_line.intersection(
create_offset_line(intersection.closest_line, intersection, scaled_spacing),
Point intersection_point; &hook_start);
bool intersection_found = intersection.intersect_line.intersection(hook_line_offset, &intersection_point);
assert(intersection_found); assert(intersection_found);
Line hook_forward(intersection_point, intersection_point + hook_vector); Vec2d hook_vector_norm = intersection.closest_line.vector().cast<double>().normalized();
Line hook_backward(intersection_point, intersection_point - hook_vector); Vector hook_vector = (hook_length * hook_vector_norm).cast<coord_t>();
Line hook_forward(hook_start, hook_start + hook_vector);
auto filter_itself = [&intersection](const auto &item) { auto filter_itself = [&intersection](const auto &item) {
const rtree_segment_t &seg = item.first; const rtree_segment_t &seg = item.first;
@ -666,51 +662,66 @@ static void add_hook(const Intersection &intersection, const Line &hook_line, co
}; };
std::vector<std::pair<rtree_segment_t, size_t>> hook_intersections; std::vector<std::pair<rtree_segment_t, size_t>> hook_intersections;
rtree.query(bgi::intersects(mk_rtree_seg(hook_forward)) && bgi::satisfies(filter_itself), rtree.query(bgi::intersects(mk_rtree_seg(hook_forward)) && bgi::satisfies(filter_itself), std::back_inserter(hook_intersections));
std::back_inserter(hook_intersections));
auto max_hook_length = [&hook_intersections, &hook_length](const Line &hook) { Point hook_end;
coord_t max_length = hook_length; if (hook_intersections.empty()) {
// The hook is not limited by another infill line. Extrude it in its full length.
hook_end = hook_forward.b;
} else {
// 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) {
// No hook is longer than hook_length, there shouldn't be any intersection closer than that.
auto max_length = double(hook_length);
auto update_max_length = [&max_length](double d) {
if (d > 0. && d < max_length)
max_length = d;
};
for (const auto &hook_intersection : hook_intersections) { for (const auto &hook_intersection : hook_intersections) {
const rtree_segment_t &segment = hook_intersection.first; const rtree_segment_t &segment = hook_intersection.first;
double dist = Line::distance_to(hook.a, Point(bg::get<0, 0>(segment), bg::get<0, 1>(segment)), // Segment start and end points.
Point(bg::get<1, 0>(segment), bg::get<1, 1>(segment))); Vec2d pt2(bg::get<0, 0>(segment), bg::get<0, 1>(segment));
max_length = std::min(coord_t(dist), max_length); Vec2d pt2b(bg::get<1, 0>(segment), bg::get<1, 1>(segment));
// Segment vector.
Vec2d dir2 = pt2b - pt2;
// Find intersection of (pt, dir) with (pt2, dir2), where dir is normalized.
double denom = cross2(dir, dir2);
if (std::abs(denom) < EPSILON) {
update_max_length((pt2 - pt).dot(dir));
update_max_length((pt2b - pt).dot(dir));
} else
update_max_length(cross2(pt2 - pt, dir2) / denom);
} }
return max_length; return max_length;
}; };
Line hook_final; // There is not enough space for the full hook length, try the opposite direction.
if (hook_intersections.empty()) { Vec2d hook_startf = hook_start.cast<double>();
hook_final = std::move(hook_forward); double hook_forward_max_length = max_hook_length(hook_startf, hook_vector_norm, hook_intersections);
} else {
// There is not enough space for the hook, try another direction
coord_t hook_forward_max_length = max_hook_length(hook_forward);
hook_intersections.clear(); hook_intersections.clear();
rtree.query(bgi::intersects(mk_rtree_seg(hook_backward)) && bgi::satisfies(filter_itself), Line hook_backward(hook_start, hook_start - hook_vector);
std::back_inserter(hook_intersections)); rtree.query(bgi::intersects(mk_rtree_seg(hook_backward)) && bgi::satisfies(filter_itself), std::back_inserter(hook_intersections));
if (hook_intersections.empty()) { if (hook_intersections.empty()) {
hook_final = std::move(hook_backward); // The hook in the other direction is not limited by another infill line. Extrude it in its full length.
hook_end = hook_backward.b;
} else { } else {
// There is not enough space for hook in both directions, shrink the hook // There is not enough space for the full hook in both directions, take the longer one.
coord_t hook_backward_max_length = max_hook_length(hook_backward); double hook_backward_max_length = max_hook_length(hook_startf, - hook_vector_norm, hook_intersections);
if (hook_forward_max_length > hook_backward_max_length) { Vec2d hook_dir = (hook_forward_max_length > hook_backward_max_length ? hook_forward_max_length : - hook_backward_max_length) * hook_vector_norm;
Vector hook_vector_reduced = (hook_forward_max_length * hook_vector_norm).cast<coord_t>(); hook_end = hook_start + hook_dir.cast<coord_t>();
hook_final = Line(intersection_point, intersection_point + hook_vector_reduced);
} else {
Vector hook_vector_reduced = (hook_backward_max_length * hook_vector_norm).cast<coord_t>();
hook_final = Line(intersection_point, intersection_point - hook_vector_reduced);
}
} }
} }
if (intersection.forward) { if (intersection.front) {
intersection.intersect_pl->points.front() = hook_final.a; intersection.intersect_pl->points.front() = hook_start;
intersection.intersect_pl->points.emplace(intersection.intersect_pl->points.begin(), hook_final.b); intersection.intersect_pl->points.emplace(intersection.intersect_pl->points.begin(), hook_end);
} else { } else {
intersection.intersect_pl->points.back() = hook_final.a; intersection.intersect_pl->points.back() = hook_start;
intersection.intersect_pl->points.emplace_back(hook_final.b); intersection.intersect_pl->points.emplace_back(hook_end);
} }
} }
@ -719,6 +730,7 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b
rtree_t rtree; rtree_t rtree;
size_t poly_idx = 0; size_t poly_idx = 0;
for (const Polyline &poly : lines) { 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++)); rtree.insert(std::make_pair(mk_rtree_seg(poly.points.front(), poly.points.back()), poly_idx++));
} }
@ -731,24 +743,28 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b
Polyline &line = lines[line_idx]; Polyline &line = lines[line_idx];
// Lines shorter than spacing are skipped because it is needed to shrink a line by the value of spacing. // 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. // A shorter line than spacing could produce a degenerate polyline.
if (line.length() <= (scaled_spacing + SCALED_EPSILON)) continue; //FIXME we should rather remove such short infill lines earlier!
if (line.length() <= (scaled_spacing + SCALED_EPSILON))
continue;
Point front_point = line.points.front(); const Point &front_point = line.points.front();
Point back_point = line.points.back(); const Point &back_point = line.points.back();
auto filter_itself = [line_idx](const auto &item) { return item.second != line_idx; }; auto filter_itself = [line_idx](const auto &item) { return item.second != line_idx; };
// Find the nearest line from the start point of the line. // Find the nearest line from the start point of the line.
closest.clear(); closest.clear();
rtree.query(bgi::nearest(rtree_point_t(float(front_point.x()), float(front_point.y())), 1) && bgi::satisfies(filter_itself), std::back_inserter(closest)); rtree.query(bgi::nearest(mk_rtree_point(front_point), 1) && bgi::satisfies(filter_itself), std::back_inserter(closest));
if (((Line) lines[closest[0].second]).distance_to(front_point) <= 1000) if (((Line) lines[closest.front().second]).distance_to(front_point) <= 1000)
intersections.emplace_back(closest[0].second, (Line) lines[closest[0].second], front_point, line_idx, &line, (Line) line, true); // T-joint of line's front point with the 'closest' line.
intersections.push_back({ closest.front().second, (Line)lines[closest.front().second], front_point, line_idx, &line, (Line)line, true });
// Find the nearest line from the end point of the line // Find the nearest line from the end point of the line
closest.clear(); closest.clear();
rtree.query(bgi::nearest(rtree_point_t(float(back_point.x()), float(back_point.y())), 1) && bgi::satisfies(filter_itself), std::back_inserter(closest)); rtree.query(bgi::nearest(mk_rtree_point(back_point), 1) && bgi::satisfies(filter_itself), std::back_inserter(closest));
if (((Line) lines[closest[0].second]).distance_to(back_point) <= 1000) if (((Line) lines[closest.front().second]).distance_to(back_point) <= 1000)
intersections.emplace_back(closest[0].second, (Line) lines[closest[0].second], back_point, line_idx, &line, (Line) line, false); // T-joint of line's back point with the 'closest' line.
intersections.push_back({ closest.front().second, (Line)lines[closest.front().second], back_point, line_idx, &line, (Line)line, false });
} }
} }
@ -758,7 +774,7 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b
std::vector<size_t> merged_with(lines.size()); std::vector<size_t> merged_with(lines.size());
std::iota(merged_with.begin(), merged_with.end(), 0); std::iota(merged_with.begin(), merged_with.end(), 0);
// Appends the boundary polygon with all holes to rtree for detection if hooks not crossing the boundary // Appends the boundary polygon with all holes to rtree for detection to check whether hooks are not crossing the boundary
{ {
Point prev = boundary.contour.points.back(); Point prev = boundary.contour.points.back();
for (const Point &point : boundary.contour.points) { for (const Point &point : boundary.contour.points) {
@ -788,108 +804,98 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b
intersection.intersect_pl = &lines[intersection.intersect_pl_idx]; intersection.intersect_pl = &lines[intersection.intersect_pl_idx];
// After polylines are merged, it is necessary to update "forward" based on if intersect_point is the first or the last point of intersect_pl. // After polylines are merged, it is necessary to update "forward" based on if intersect_point is the first or the last point of intersect_pl.
if (!intersection.used && !intersection.intersect_pl->points.empty()) if (intersection.fresh())
intersection.forward = (intersection.intersect_pl->points.front() == intersection.intersect_point); intersection.front = intersection.intersect_pl->points.front() == intersection.intersect_point;
}; };
for (size_t min_idx = 0; min_idx < intersections.size(); ++min_idx) { // Keep intersect_line outside the loop, so it does not get reallocated.
std::vector<std::pair<Intersection, double>> intersect_line; std::vector<std::pair<Intersection*, double>> intersect_line;
Matrix2d rotation = rotation_matrix_from_vector(intersections[min_idx].closest_line.vector()); for (size_t min_idx = 0; min_idx < intersections.size();) {
intersect_line.emplace_back(intersections[min_idx], (rotation * intersections[min_idx].intersect_point.cast<double>()).x()); const Vec2d line_dir = intersections[min_idx].closest_line.vector().cast<double>();
// All the nearest points on the same line are projected on this line. Because of it, it can easily find the nearest point intersect_line.clear();
for (size_t max_idx = min_idx + 1; max_idx < intersections.size(); ++max_idx) { // 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.
if (intersections[min_idx].closest_line_idx != intersections[max_idx].closest_line_idx) break; {
const Point &p0 = intersections[min_idx].intersect_point;
intersect_line.emplace_back(intersections[max_idx], (rotation * intersections[max_idx].intersect_point.cast<double>()).x()); size_t max_idx = min_idx + 1;
intersect_line.emplace_back(&intersections[min_idx], 0.);
for (; max_idx < intersections.size() && intersections[min_idx].closest_line_idx == intersections[max_idx].closest_line_idx; ++max_idx)
intersect_line.emplace_back(&intersections[max_idx], line_dir.dot((intersections[max_idx].intersect_point - p0).cast<double>()));
min_idx = max_idx; min_idx = max_idx;
} }
if (intersect_line.size() == 1) {
assert(!intersect_line.empty()); // Simple case: The current intersection is the only one touching its adjacent line.
if (intersect_line.size() <= 1) { Intersection &first_i = *intersect_line.front().first;
// On the adjacent line is only one intersection if (first_i.fresh()) {
Intersection &first_i = intersect_line.front().first; // Try to connect left or right. If not enough space for hook_length, take the longer side.
if (first_i.used || first_i.intersect_pl->points.empty()) continue; add_hook(first_i, scale_(spacing), hook_length, rtree);
add_hook(first_i, first_i.closest_line, scale_(spacing), hook_length, rtree);
first_i.used = true; first_i.used = true;
}
continue; continue;
} }
assert(intersect_line.size() >= 2); assert(intersect_line.size() > 1);
// Sort the intersections along line_dir.
std::sort(intersect_line.begin(), intersect_line.end(), [](const auto &i1, const auto &i2) { return i1.second < i2.second; }); std::sort(intersect_line.begin(), intersect_line.end(), [](const auto &i1, const auto &i2) { return i1.second < i2.second; });
for (size_t first_idx = 0; first_idx < intersect_line.size(); ++first_idx) { for (size_t first_idx = 0; first_idx < intersect_line.size(); ++ first_idx) {
Intersection &first_i = intersect_line[first_idx].first; Intersection &first_i = *intersect_line[first_idx].first;
Intersection &nearest_i = *get_nearest_intersection(intersect_line, first_idx); if (! first_i.fresh())
// The intersection has been processed, or the polyline has been merged to another polyline.
continue;
// Get the previous or next intersection on the same line, pick the closer one.
Intersection &nearest_i = *get_nearest_intersection(intersect_line, first_idx);
update_merged_polyline(first_i); update_merged_polyline(first_i);
update_merged_polyline(nearest_i); update_merged_polyline(nearest_i);
// The intersection has been processed, or the polyline has been merge to another polyline.
if (first_i.used || first_i.intersect_pl->points.empty()) continue;
// A line between two intersections points // A line between two intersections points
Line intersection_line(first_i.intersect_point, nearest_i.intersect_point); Line offset_line = create_offset_line(Line(first_i.intersect_point, nearest_i.intersect_point), first_i, scale_(spacing));
Line offset_line = create_offset_line(intersection_line, first_i, scale_(spacing));
double intersection_line_length = intersection_line.length();
// Check if both intersections lie on the offset_line and simultaneously get their points of intersecting. // 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 // These points are used as start and end of the hook
Point first_i_point, nearest_i_point; Point first_i_point, nearest_i_point;
if (first_i.intersect_line.intersection(offset_line, &first_i_point) && if (first_i.intersect_line.intersection(offset_line, &first_i_point) &&
nearest_i.intersect_line.intersection(offset_line, &nearest_i_point)) { nearest_i.intersect_line.intersection(offset_line, &nearest_i_point)) {
// Both intersections are so close that their polylines can be connected if (nearest_i.fresh() && (nearest_i_point - first_i_point).cast<double>().squaredNorm() <= Slic3r::sqr(3. * hook_length)) {
if (!nearest_i.used && !nearest_i.intersect_pl->points.empty() && intersection_line_length <= 2 * hook_length) { // Both intersections are so close that their polylines can be connected.
if (first_i.intersect_pl_idx == nearest_i.intersect_pl_idx) { if (first_i.intersect_pl_idx == nearest_i.intersect_pl_idx) {
// Both intersections are on the same polyline // Both intersections are on the same polyline, that means a loop is being closed.
if (!first_i.forward) { std::swap(first_i_point, nearest_i_point); } 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.front() = first_i_point;
first_i.intersect_pl->points.back() = nearest_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); first_i.intersect_pl->points.emplace(first_i.intersect_pl->points.begin(), nearest_i_point);
} else { } else {
// Both intersections are on different polylines // Both intersections are on different polylines
Points merge_polyline_points; Points &first_points = first_i.intersect_pl->points;
size_t first_polyline_size = first_i.intersect_pl->points.size(); Points &second_points = nearest_i.intersect_pl->points;
size_t nearest_polyline_size = nearest_i.intersect_pl->points.size(); first_points.reserve(first_points.size() + second_points.size());
merge_polyline_points.reserve(first_polyline_size + nearest_polyline_size); if (first_i.front)
std::reverse(first_points.begin(), first_points.end());
if (first_i.forward) { first_points.back() = first_i_point;
if (nearest_i.forward) first_points.emplace_back(nearest_i_point);
for (auto it = nearest_i.intersect_pl->points.rbegin(); it != nearest_i.intersect_pl->points.rend(); ++it) if (nearest_i.front)
merge_polyline_points.emplace_back(*it); first_points.insert(first_points.end(), second_points.begin() + 1, second_points.end());
else else
for (const Point &point : nearest_i.intersect_pl->points) first_points.insert(first_points.end(), second_points.rbegin() + 1, second_points.rend());
merge_polyline_points.emplace_back(point); // Keep the polyline at the lower index slot.
if (first_i.intersect_pl_idx < nearest_i.intersect_pl_idx) {
append(merge_polyline_points, std::move(first_i.intersect_pl->points)); second_points.clear();
merge_polyline_points[nearest_polyline_size - 1] = nearest_i_point;
merge_polyline_points[nearest_polyline_size] = first_i_point;
} else {
append(merge_polyline_points, std::move(first_i.intersect_pl->points));
if (nearest_i.forward)
for (const Point &point : nearest_i.intersect_pl->points)
merge_polyline_points.emplace_back(point);
else
for (auto it = nearest_i.intersect_pl->points.rbegin(); it != nearest_i.intersect_pl->points.rend(); ++it)
merge_polyline_points.emplace_back(*it);
merge_polyline_points[first_polyline_size - 1] = first_i_point;
merge_polyline_points[first_polyline_size] = nearest_i_point;
}
merged_with[nearest_i.intersect_pl_idx] = merged_with[first_i.intersect_pl_idx]; merged_with[nearest_i.intersect_pl_idx] = merged_with[first_i.intersect_pl_idx];
nearest_i.intersect_pl->points.clear();
first_i.intersect_pl->points = merge_polyline_points;
}
first_i.used = true;
nearest_i.used = true;
} else { } else {
add_hook(first_i, first_i.closest_line, scale_(spacing), hook_length, rtree); second_points = std::move(first_points);
first_i.used = true; first_points.clear();
merged_with[first_i.intersect_pl_idx] = merged_with[nearest_i.intersect_pl_idx];
} }
} }
nearest_i.used = true;
} else
// Try to connect left or right. If not enough space for hook_length, take the longer side.
add_hook(first_i, scale_(spacing), hook_length, rtree);
first_i.used = true;
} else {
// The first & last point should always be found.
assert(false);
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -124,7 +124,9 @@ protected:
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const; virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;
public: public:
static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, double spacing, const FillParams &params, const int hook_length = 0); static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const double spacing, const FillParams &params, const int hook_length = 0);
static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams &params, const int hook_length = 0);
static void connect_infill(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams &params, const int hook_length = 0);
static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance);

View File

@ -7,12 +7,14 @@
#include <random> #include <random>
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#include <boost/log/trivial.hpp>
#include <boost/static_assert.hpp> #include <boost/static_assert.hpp>
#include "../ClipperUtils.hpp" #include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp" #include "../ExPolygon.hpp"
#include "../Geometry.hpp" #include "../Geometry.hpp"
#include "../Surface.hpp" #include "../Surface.hpp"
#include "../ShortestPath.hpp"
#include "FillRectilinear2.hpp" #include "FillRectilinear2.hpp"
@ -128,6 +130,13 @@ struct SegmentIntersection
return coord_t(p / int64_t(pos_q)); return coord_t(p / int64_t(pos_q));
} }
// Left vertical line / contour intersection point.
// null if next_on_contour_vertical.
int32_t prev_on_contour { 0 };
// Right vertical line / contour intersection point.
// If next_on_contour_vertical, then then next_on_contour contains next contour point on the same vertical line.
int32_t next_on_contour { 0 };
// Kind of intersection. With the original contour, or with the inner offestted contour? // Kind of intersection. With the original contour, or with the inner offestted contour?
// A vertical segment will be at least intersected by OUTER_LOW, OUTER_HIGH, // A vertical segment will be at least intersected by OUTER_LOW, OUTER_HIGH,
// but it could be intersected with OUTER_LOW, INNER_LOW, INNER_HIGH, OUTER_HIGH, // but it could be intersected with OUTER_LOW, INNER_LOW, INNER_HIGH, OUTER_HIGH,
@ -141,13 +150,6 @@ struct SegmentIntersection
}; };
SegmentIntersectionType type { UNKNOWN }; SegmentIntersectionType type { UNKNOWN };
// Left vertical line / contour intersection point.
// null if next_on_contour_vertical.
int32_t prev_on_contour { 0 };
// Right vertical line / contour intersection point.
// If next_on_contour_vertical, then then next_on_contour contains next contour point on the same vertical line.
int32_t next_on_contour { 0 };
enum class LinkType : uint8_t { enum class LinkType : uint8_t {
// Horizontal link (left or right). // Horizontal link (left or right).
Horizontal, Horizontal,
@ -383,30 +385,31 @@ public:
const ExPolygon &expolygon, const ExPolygon &expolygon,
float angle, float angle,
coord_t aoffset1, coord_t aoffset1,
coord_t aoffset2) // If the 2nd offset is zero, then it is ignored and only OUTER_LOW / OUTER_HIGH intersections are
// populated into vertical intersection lines.
coord_t aoffset2 = 0)
{ {
// Copy and rotate the source polygons. // Copy and rotate the source polygons.
polygons_src = expolygon; polygons_src = expolygon;
if (angle != 0.f) {
polygons_src.contour.rotate(angle); polygons_src.contour.rotate(angle);
for (Polygons::iterator it = polygons_src.holes.begin(); it != polygons_src.holes.end(); ++ it) for (Polygon &hole : polygons_src.holes)
it->rotate(angle); hole.rotate(angle);
}
double mitterLimit = 3.; double mitterLimit = 3.;
// for the infill pattern, don't cut the corners. // for the infill pattern, don't cut the corners.
// default miterLimt = 3 // default miterLimt = 3
//double mitterLimit = 10.; //double mitterLimit = 10.;
assert(aoffset1 < 0); assert(aoffset1 < 0);
assert(aoffset2 < 0); assert(aoffset2 <= 0);
assert(aoffset2 < aoffset1); assert(aoffset2 == 0 || aoffset2 < aoffset1);
// bool sticks_removed = // bool sticks_removed =
remove_sticks(polygons_src); remove_sticks(polygons_src);
// if (sticks_removed) printf("Sticks removed!\n"); // if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!";
polygons_outer = offset(polygons_src, float(aoffset1), polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, mitterLimit);
ClipperLib::jtMiter, if (aoffset2 < 0)
mitterLimit); polygons_inner = offset(polygons_outer, float(aoffset2 - aoffset1), ClipperLib::jtMiter, mitterLimit);
polygons_inner = offset(polygons_outer, float(aoffset2 - aoffset1),
ClipperLib::jtMiter,
mitterLimit);
// Filter out contours with zero area or small area, contours with 2 points only. // Filter out contours with zero area or small area, contours with 2 points only.
const double min_area_threshold = 0.01 * aoffset2 * aoffset2; const double min_area_threshold = 0.01 * aoffset2 * aoffset2;
remove_small(polygons_outer, min_area_threshold); remove_small(polygons_outer, min_area_threshold);
@ -424,6 +427,18 @@ public:
} }
} }
ExPolygonWithOffset(const ExPolygonWithOffset &rhs, float angle) : ExPolygonWithOffset(rhs) {
if (angle != 0.f) {
this->polygons_src.contour.rotate(angle);
for (Polygon &hole : this->polygons_src.holes)
hole.rotate(angle);
for (Polygon &poly : this->polygons_outer)
poly.rotate(angle);
for (Polygon &poly : this->polygons_inner)
poly.rotate(angle);
}
}
// Any contour with offset1 // Any contour with offset1
bool is_contour_outer(size_t idx) const { return idx < n_contours_outer; } bool is_contour_outer(size_t idx) const { return idx < n_contours_outer; }
// Any contour with offset2 // Any contour with offset2
@ -2644,7 +2659,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP
Point refpt = rotate_vector.second.rotated(- rotate_vector.first); Point refpt = rotate_vector.second.rotated(- rotate_vector.first);
// _align_to_grid will not work correctly with positive pattern_shift. // _align_to_grid will not work correctly with positive pattern_shift.
coord_t pattern_shift_scaled = coord_t(scale_(pattern_shift)) % line_spacing; coord_t pattern_shift_scaled = coord_t(scale_(pattern_shift)) % line_spacing;
refpt(0) -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled);
bounding_box.merge(_align_to_grid( bounding_box.merge(_align_to_grid(
bounding_box.min, bounding_box.min,
Point(line_spacing, line_spacing), Point(line_spacing, line_spacing),
@ -2747,12 +2762,93 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP
return true; return true;
} }
#define FILL_MULTIPLE_SWEEPS_NEW
#ifdef FILL_MULTIPLE_SWEEPS_NEW
bool FillRectilinear2::fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out)
{
assert(sweep_params.size() > 1);
assert(! params.full_infill());
params.density /= double(sweep_params.size());
assert(params.density > 0.0001f && params.density <= 1.f);
ExPolygonWithOffset poly_with_offset_base(surface->expolygon, 0, float(scale_(this->overlap - 0.5 * this->spacing)));
if (poly_with_offset_base.n_contours == 0)
// Not a single infill line fits.
return true;
Polylines fill_lines;
coord_t line_spacing = coord_t(scale_(this->spacing) / params.density);
std::pair<float, Point> rotate_vector = this->_infill_direction(surface);
for (const SweepParams &sweep : sweep_params) {
size_t n_fill_lines_initial = fill_lines.size();
// Rotate polygons so that we can work with vertical lines here
double angle = rotate_vector.first + sweep.angle_base;
ExPolygonWithOffset poly_with_offset(poly_with_offset_base, - angle);
BoundingBox bounding_box = poly_with_offset.bounding_box_src();
// extend bounding box so that our pattern will be aligned with other layers
// Transform the reference point to the rotated coordinate system.
Point refpt = rotate_vector.second.rotated(- angle);
// _align_to_grid will not work correctly with positive pattern_shift.
coord_t pattern_shift_scaled = coord_t(scale_(sweep.pattern_shift)) % line_spacing;
refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled);
bounding_box.merge(_align_to_grid(bounding_box.min, Point(line_spacing, line_spacing), refpt));
// Intersect a set of euqally spaced vertical lines wiht expolygon.
// n_vlines = ceil(bbox_width / line_spacing)
const size_t n_vlines = (bounding_box.max.x() - bounding_box.min.x() + line_spacing - 1) / line_spacing;
const double cos_a = cos(angle);
const double sin_a = sin(angle);
for (const SegmentedIntersectionLine &vline : slice_region_by_vertical_lines(poly_with_offset, n_vlines, bounding_box.min.x(), line_spacing)) {
for (auto it = vline.intersections.begin(); it != vline.intersections.end();) {
auto it_low = it ++;
assert(it_low->type == SegmentIntersection::OUTER_LOW);
if (it_low->type != SegmentIntersection::OUTER_LOW)
continue;
auto it_high = it;
assert(it_high->type == SegmentIntersection::OUTER_HIGH);
if (it_high->type == SegmentIntersection::OUTER_HIGH) {
fill_lines.emplace_back(Point(vline.pos, it_low->pos()).rotated(cos_a, sin_a), Point(vline.pos, it_high->pos()).rotated(cos_a, sin_a));
++ it;
}
}
}
}
if (fill_lines.size() > 1)
fill_lines = chain_polylines(std::move(fill_lines));
if (params.dont_connect || fill_lines.size() <= 1)
append(polylines_out, std::move(fill_lines));
else {
// coord_t hook_length = 0;
coord_t hook_length = coord_t(scale_(this->spacing)) * 5;
connect_infill(std::move(fill_lines), poly_with_offset_base.polygons_outer, get_extents(surface->expolygon.contour), polylines_out, this->spacing, params, hook_length);
}
return true;
}
#else
bool FillRectilinear2::fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out)
{
params.density /= double(sweep_params.size());
bool success = true;
int idx = 0;
for (const SweepParams &sweep_param : sweep_params) {
if (++ idx == 3)
params.dont_connect = true;
success &= this->fill_surface_by_lines(surface, params, sweep_param.angle_base, sweep_param.pattern_shift, polylines_out);
}
return success;
}
#endif
Polylines FillRectilinear2::fill_surface(const Surface *surface, const FillParams &params) Polylines FillRectilinear2::fill_surface(const Surface *surface, const FillParams &params)
{ {
Polylines polylines_out; Polylines polylines_out;
if (! fill_surface_by_lines(surface, params, 0.f, 0.f, polylines_out)) { if (! fill_surface_by_lines(surface, params, 0.f, 0.f, polylines_out))
printf("FillRectilinear2::fill_surface() failed to fill a region.\n"); BOOST_LOG_TRIVIAL(error) << "FillRectilinear2::fill_surface() failed to fill a region.";
}
return polylines_out; return polylines_out;
} }
@ -2761,72 +2857,53 @@ Polylines FillMonotonic::fill_surface(const Surface *surface, const FillParams &
FillParams params2 = params; FillParams params2 = params;
params2.monotonic = true; params2.monotonic = true;
Polylines polylines_out; Polylines polylines_out;
if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out)) { if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out))
printf("FillMonotonic::fill_surface() failed to fill a region.\n"); BOOST_LOG_TRIVIAL(error) << "FillMonotonous::fill_surface() failed to fill a region.";
}
return polylines_out; return polylines_out;
} }
Polylines FillGrid2::fill_surface(const Surface *surface, const FillParams &params) Polylines FillGrid2::fill_surface(const Surface *surface, const FillParams &params)
{ {
// Each linear fill covers half of the target coverage.
FillParams params2 = params;
params2.density *= 0.5f;
Polylines polylines_out; Polylines polylines_out;
if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out) || if (! this->fill_surface_by_multilines(
! fill_surface_by_lines(surface, params2, float(M_PI / 2.), 0.f, polylines_out)) { surface, params,
printf("FillGrid2::fill_surface() failed to fill a region.\n"); { { 0.f, 0.f }, { float(M_PI / 2.), 0.f } },
} polylines_out))
BOOST_LOG_TRIVIAL(error) << "FillGrid2::fill_surface() failed to fill a region.";
return polylines_out; return polylines_out;
} }
Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams &params) Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams &params)
{ {
// Each linear fill covers 1/3 of the target coverage.
FillParams params2 = params;
params2.density *= 0.333333333f;
FillParams params3 = params2;
params3.dont_connect = true;
Polylines polylines_out; Polylines polylines_out;
if (! fill_surface_by_lines(surface, params2, 0.f, 0., polylines_out) || if (! this->fill_surface_by_multilines(
! fill_surface_by_lines(surface, params2, float(M_PI / 3.), 0., polylines_out) || surface, params,
! fill_surface_by_lines(surface, params3, float(2. * M_PI / 3.), 0., polylines_out)) { { { 0.f, 0.f }, { float(M_PI / 3.), 0.f }, { float(2. * M_PI / 3.), 0. } },
printf("FillTriangles::fill_surface() failed to fill a region.\n"); polylines_out))
} BOOST_LOG_TRIVIAL(error) << "FillTriangles::fill_surface() failed to fill a region.";
return polylines_out; return polylines_out;
} }
Polylines FillStars::fill_surface(const Surface *surface, const FillParams &params) Polylines FillStars::fill_surface(const Surface *surface, const FillParams &params)
{ {
// Each linear fill covers 1/3 of the target coverage.
FillParams params2 = params;
params2.density *= 0.333333333f;
FillParams params3 = params2;
params3.dont_connect = true;
Polylines polylines_out; Polylines polylines_out;
if (! fill_surface_by_lines(surface, params2, 0.f, 0., polylines_out) || if (! this->fill_surface_by_multilines(
! fill_surface_by_lines(surface, params2, float(M_PI / 3.), 0., polylines_out) || surface, params,
! fill_surface_by_lines(surface, params3, float(2. * M_PI / 3.), 0.5 * this->spacing / params2.density, polylines_out)) { { { 0.f, 0.f }, { float(M_PI / 3.), 0.f }, { float(2. * M_PI / 3.), float((3./2.) * this->spacing / params.density) } },
printf("FillStars::fill_surface() failed to fill a region.\n"); polylines_out))
} BOOST_LOG_TRIVIAL(error) << "FillStars::fill_surface() failed to fill a region.";
return polylines_out; return polylines_out;
} }
Polylines FillCubic::fill_surface(const Surface *surface, const FillParams &params) Polylines FillCubic::fill_surface(const Surface *surface, const FillParams &params)
{ {
// Each linear fill covers 1/3 of the target coverage.
FillParams params2 = params;
params2.density *= 0.333333333f;
FillParams params3 = params2;
params3.dont_connect = true;
Polylines polylines_out; Polylines polylines_out;
coordf_t dx = sqrt(0.5) * z; coordf_t dx = sqrt(0.5) * z;
if (! fill_surface_by_lines(surface, params2, 0.f, float(dx), polylines_out) || if (! this->fill_surface_by_multilines(
! fill_surface_by_lines(surface, params2, float(M_PI / 3.), - float(dx), polylines_out) || surface, params,
// Rotated by PI*2/3 + PI to achieve reverse sloping wall. { { 0.f, float(dx) }, { float(M_PI / 3.), - float(dx) }, { float(M_PI * 2. / 3.), float(dx) } },
! fill_surface_by_lines(surface, params3, float(M_PI * 2. / 3.), float(dx), polylines_out)) { polylines_out))
printf("FillCubic::fill_surface() failed to fill a region.\n"); BOOST_LOG_TRIVIAL(error) << "FillCubic::fill_surface() failed to fill a region.";
}
return polylines_out; return polylines_out;
} }

View File

@ -17,7 +17,16 @@ public:
virtual Polylines fill_surface(const Surface *surface, const FillParams &params); virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected: protected:
// Fill by single directional lines, interconnect the lines along perimeters.
bool fill_surface_by_lines(const Surface *surface, const FillParams &params, float angleBase, float pattern_shift, Polylines &polylines_out); bool fill_surface_by_lines(const Surface *surface, const FillParams &params, float angleBase, float pattern_shift, Polylines &polylines_out);
// Fill by multiple sweeps of differing directions.
struct SweepParams {
float angle_base;
float pattern_shift;
};
bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out);
}; };
class FillMonotonic : public FillRectilinear2 class FillMonotonic : public FillRectilinear2

View File

@ -338,19 +338,19 @@ double rad2deg_dir(double angle)
return rad2deg(angle); return rad2deg(angle);
} }
Point circle_taubin_newton(const Points::const_iterator& input_begin, const Points::const_iterator& input_end, size_t cycles) Point circle_center_taubin_newton(const Points::const_iterator& input_begin, const Points::const_iterator& input_end, size_t cycles)
{ {
Vec2ds tmp; Vec2ds tmp;
tmp.reserve(std::distance(input_begin, input_end)); tmp.reserve(std::distance(input_begin, input_end));
std::transform(input_begin, input_end, std::back_inserter(tmp), [] (const Point& in) { return unscale(in); } ); std::transform(input_begin, input_end, std::back_inserter(tmp), [] (const Point& in) { return unscale(in); } );
Vec2d center = circle_taubin_newton(tmp.cbegin(), tmp.end(), cycles); Vec2d center = circle_center_taubin_newton(tmp.cbegin(), tmp.end(), cycles);
return Point::new_scale(center.x(), center.y()); return Point::new_scale(center.x(), center.y());
} }
/// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126 /// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126
/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end /// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end
/// lie on. /// lie on.
Vec2d circle_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles) Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles)
{ {
// calculate the centroid of the data set // calculate the centroid of the data set
const Vec2d sum = std::accumulate(input_begin, input_end, Vec2d(0,0)); const Vec2d sum = std::accumulate(input_begin, input_end, Vec2d(0,0));

View File

@ -202,17 +202,16 @@ inline double ray_point_distance(const Line &iline, const Point &ipt)
// Based on Liang-Barsky function by Daniel White @ http://www.skytopia.com/project/articles/compsci/clipping.html // Based on Liang-Barsky function by Daniel White @ http://www.skytopia.com/project/articles/compsci/clipping.html
template<typename T> template<typename T>
inline bool liang_barsky_line_clipping( inline bool liang_barsky_line_clipping_interval(
// Start and end points of the source line, result will be stored there as well. // Start and end points of the source line, result will be stored there as well.
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x0, const Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x0,
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x1, const Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &v,
// Bounding box to clip with. // Bounding box to clip with.
const BoundingBoxBase<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &bbox) const BoundingBoxBase<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &bbox,
std::pair<double, double> &out_interval)
{ {
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> v = x1 - x0;
double t0 = 0.0; double t0 = 0.0;
double t1 = 1.0; double t1 = 1.0;
// Traverse through left, right, bottom, top edges. // Traverse through left, right, bottom, top edges.
for (int edge = 0; edge < 4; ++ edge) for (int edge = 0; edge < 4; ++ edge)
{ {
@ -249,10 +248,26 @@ inline bool liang_barsky_line_clipping(
} }
} }
} }
out_interval.first = t0;
out_interval.second = t1;
return true;
}
template<typename T>
inline bool liang_barsky_line_clipping(
// Start and end points of the source line, result will be stored there as well.
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x0,
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x1,
// Bounding box to clip with.
const BoundingBoxBase<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &bbox)
{
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> v = x1 - x0;
std::pair<double, double> interval;
if (liang_barsky_line_clipping_interval(x0, v, bbox, interval)) {
// Clipped successfully. // Clipped successfully.
x1 = x0 + t1 * v; x1 = x0 + interval.second * v;
x0 += t0 * v; x0 += interval.first * v;
}
return true; return true;
} }
@ -273,6 +288,35 @@ bool liang_barsky_line_clipping(
return liang_barsky_line_clipping(x0clip, x1clip, bbox); return liang_barsky_line_clipping(x0clip, x1clip, bbox);
} }
// Ugly named variant, that accepts the squared line
// Don't call me with a nearly zero length vector!
template<typename T>
int ray_circle_intersections_r2_lv2_c(T r2, T a, T b, T lv2, T c, std::pair<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>, Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &out)
{
T x0 = - a * c / lv2;
T y0 = - b * c / lv2;
T d = r2 - c * c / lv2;
if (d < T(0))
return 0;
T mult = sqrt(d / lv2);
out.first.x() = x0 + b * mult;
out.first.y() = y0 - a * mult;
out.second.x() = x0 - b * mult;
out.second.y() = y0 + a * mult;
return mult == T(0) ? 1 : 2;
}
template<typename T>
int ray_circle_intersections(T r, T a, T b, T c, std::pair<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>, Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &out)
{
T lv2 = a * a + b * b;
if (lv2 < T(SCALED_EPSILON * SCALED_EPSILON)) {
//FIXME what is the correct epsilon?
// What if the line touches the circle?
return false;
}
return ray_circle_intersections_r2_lv2_c2(r * r, a, b, a * a + b * b, c, out);
}
Pointf3s convex_hull(Pointf3s points); Pointf3s convex_hull(Pointf3s points);
Polygon convex_hull(Points points); Polygon convex_hull(Points points);
Polygon convex_hull(const Polygons &polygons); Polygon convex_hull(const Polygons &polygons);
@ -298,12 +342,12 @@ template<typename T> T angle_to_0_2PI(T angle)
} }
/// Find the center of the circle corresponding to the vector of Points as an arc. /// Find the center of the circle corresponding to the vector of Points as an arc.
Point circle_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20); Point circle_center_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20);
inline Point circle_taubin_newton(const Points& input, size_t cycles = 20) { return circle_taubin_newton(input.cbegin(), input.cend(), cycles); } inline Point circle_center_taubin_newton(const Points& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); }
/// Find the center of the circle corresponding to the vector of Pointfs as an arc. /// Find the center of the circle corresponding to the vector of Pointfs as an arc.
Vec2d circle_taubin_newton(const Vec2ds::const_iterator& input_start, const Vec2ds::const_iterator& input_end, size_t cycles = 20); Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_start, const Vec2ds::const_iterator& input_end, size_t cycles = 20);
inline Vec2d circle_taubin_newton(const Vec2ds& input, size_t cycles = 20) { return circle_taubin_newton(input.cbegin(), input.cend(), cycles); } inline Vec2d circle_center_taubin_newton(const Vec2ds& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); }
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval); void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval);

View File

@ -132,6 +132,7 @@ public:
void rotate(double angle, const Point &center); void rotate(double angle, const Point &center);
Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; }
Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; }
Point rotated(double angle, const Point &center) const { Point res(*this); res.rotate(angle, center); return res; } Point rotated(double angle, const Point &center) const { Point res(*this); res.rotate(angle, center); return res; }
int nearest_point_index(const Points &points) const; int nearest_point_index(const Points &points) const;
int nearest_point_index(const PointConstPtrs &points) const; int nearest_point_index(const PointConstPtrs &points) const;
@ -174,6 +175,12 @@ inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON
return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon;
} }
inline Point lerp(const Point &a, const Point &b, double t)
{
assert((t >= -EPSILON) && (t <= 1. + EPSILON));
return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>();
}
namespace int128 { namespace int128 {
// Exact orientation predicate, // Exact orientation predicate,
// returns +1: CCW, 0: collinear, -1: CW. // returns +1: CCW, 0: collinear, -1: CW.

View File

@ -103,12 +103,6 @@ enum Axis {
NUM_AXES_WITH_UNKNOWN, NUM_AXES_WITH_UNKNOWN,
}; };
template <class T>
inline void append_to(std::vector<T> &dst, const std::vector<T> &src)
{
dst.insert(dst.end(), src.begin(), src.end());
}
template <typename T> template <typename T>
inline void append(std::vector<T>& dest, const std::vector<T>& src) inline void append(std::vector<T>& dest, const std::vector<T>& src)
{ {
@ -123,8 +117,34 @@ inline void append(std::vector<T>& dest, std::vector<T>&& src)
{ {
if (dest.empty()) if (dest.empty())
dest = std::move(src); dest = std::move(src);
else else {
dest.reserve(dest.size() + src.size());
std::move(std::begin(src), std::end(src), std::back_inserter(dest)); std::move(std::begin(src), std::end(src), std::back_inserter(dest));
}
src.clear();
src.shrink_to_fit();
}
// Append the source in reverse.
template <typename T>
inline void append_reversed(std::vector<T>& dest, const std::vector<T>& src)
{
if (dest.empty())
dest = src;
else
dest.insert(dest.end(), src.rbegin(), src.rend());
}
// Append the source in reverse.
template <typename T>
inline void append_reversed(std::vector<T>& dest, std::vector<T>&& src)
{
if (dest.empty())
dest = std::move(src);
else {
dest.reserve(dest.size() + src.size());
std::move(std::rbegin(src), std::rend(src), std::back_inserter(dest));
}
src.clear(); src.clear();
src.shrink_to_fit(); src.shrink_to_fit();
} }

View File

@ -168,21 +168,21 @@ SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
WHEN("Circle fit is called on the entire array") { WHEN("Circle fit is called on the entire array") {
Vec2d result_center(0,0); Vec2d result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample); result_center = Geometry::circle_center_taubin_newton(sample);
THEN("A center point of -6,0 is returned.") { THEN("A center point of -6,0 is returned.") {
REQUIRE(is_approx(result_center, expected_center)); REQUIRE(is_approx(result_center, expected_center));
} }
} }
WHEN("Circle fit is called on the first four points") { WHEN("Circle fit is called on the first four points") {
Vec2d result_center(0,0); Vec2d result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4); result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of -6,0 is returned.") { THEN("A center point of -6,0 is returned.") {
REQUIRE(is_approx(result_center, expected_center)); REQUIRE(is_approx(result_center, expected_center));
} }
} }
WHEN("Circle fit is called on the middle four points") { WHEN("Circle fit is called on the middle four points") {
Vec2d result_center(0,0); Vec2d result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6); result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of -6,0 is returned.") { THEN("A center point of -6,0 is returned.") {
REQUIRE(is_approx(result_center, expected_center)); REQUIRE(is_approx(result_center, expected_center));
} }
@ -199,21 +199,21 @@ SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
WHEN("Circle fit is called on the entire array") { WHEN("Circle fit is called on the entire array") {
Vec2d result_center(0,0); Vec2d result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample); result_center = Geometry::circle_center_taubin_newton(sample);
THEN("A center point of 3,9 is returned.") { THEN("A center point of 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center)); REQUIRE(is_approx(result_center, expected_center));
} }
} }
WHEN("Circle fit is called on the first four points") { WHEN("Circle fit is called on the first four points") {
Vec2d result_center(0,0); Vec2d result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4); result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of 3,9 is returned.") { THEN("A center point of 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center)); REQUIRE(is_approx(result_center, expected_center));
} }
} }
WHEN("Circle fit is called on the middle four points") { WHEN("Circle fit is called on the middle four points") {
Vec2d result_center(0,0); Vec2d result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6); result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of 3,9 is returned.") { THEN("A center point of 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center)); REQUIRE(is_approx(result_center, expected_center));
} }
@ -230,21 +230,21 @@ SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
WHEN("Circle fit is called on the entire array") { WHEN("Circle fit is called on the entire array") {
Point result_center(0,0); Point result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample); result_center = Geometry::circle_center_taubin_newton(sample);
THEN("A center point of scaled 3,9 is returned.") { THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center)); REQUIRE(is_approx(result_center, expected_center));
} }
} }
WHEN("Circle fit is called on the first four points") { WHEN("Circle fit is called on the first four points") {
Point result_center(0,0); Point result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin(), sample.cbegin()+4); result_center = Geometry::circle_center_taubin_newton(sample.cbegin(), sample.cbegin()+4);
THEN("A center point of scaled 3,9 is returned.") { THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center)); REQUIRE(is_approx(result_center, expected_center));
} }
} }
WHEN("Circle fit is called on the middle four points") { WHEN("Circle fit is called on the middle four points") {
Point result_center(0,0); Point result_center(0,0);
result_center = Geometry::circle_taubin_newton(sample.cbegin()+2, sample.cbegin()+6); result_center = Geometry::circle_center_taubin_newton(sample.cbegin()+2, sample.cbegin()+6);
THEN("A center point of scaled 3,9 is returned.") { THEN("A center point of scaled 3,9 is returned.") {
REQUIRE(is_approx(result_center, expected_center)); REQUIRE(is_approx(result_center, expected_center));
} }