#include "Geometry.hpp" #include "ClipperUtils.hpp" #include "ExPolygon.hpp" #include "Line.hpp" #include "PolylineCollection.hpp" #include "clipper.hpp" #include #include #include #include #include #include #include #include #include #ifdef SLIC3R_DEBUG #include "SVG.hpp" #endif #ifdef SLIC3R_DEBUG namespace boost { namespace polygon { // The following code for the visualization of the boost Voronoi diagram is based on: // // Boost.Polygon library voronoi_graphic_utils.hpp header file // Copyright Andrii Sydorchuk 2010-2012. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) template class voronoi_visual_utils { public: // Discretize parabolic Voronoi edge. // Parabolic Voronoi edges are always formed by one point and one segment // from the initial input set. // // Args: // point: input point. // segment: input segment. // max_dist: maximum discretization distance. // discretization: point discretization of the given Voronoi edge. // // Template arguments: // InCT: coordinate type of the input geometries (usually integer). // Point: point type, should model point concept. // Segment: segment type, should model segment concept. // // Important: // discretization should contain both edge endpoints initially. template class Point, template class Segment> static typename enable_if< typename gtl_and< typename gtl_if< typename is_point_concept< typename geometry_concept< Point >::type >::type >::type, typename gtl_if< typename is_segment_concept< typename geometry_concept< Segment >::type >::type >::type >::type, void >::type discretize( const Point& point, const Segment& segment, const CT max_dist, std::vector< Point >* discretization) { // Apply the linear transformation to move start point of the segment to // the point with coordinates (0, 0) and the direction of the segment to // coincide the positive direction of the x-axis. CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment))); CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment))); CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y; // Compute x-coordinates of the endpoints of the edge // in the transformed space. CT projection_start = sqr_segment_length * get_point_projection((*discretization)[0], segment); CT projection_end = sqr_segment_length * get_point_projection((*discretization)[1], segment); // Compute parabola parameters in the transformed space. // Parabola has next representation: // f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y). CT point_vec_x = cast(x(point)) - cast(x(low(segment))); CT point_vec_y = cast(y(point)) - cast(y(low(segment))); CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y; CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x; // Save the last point. Point last_point = (*discretization)[1]; discretization->pop_back(); // Use stack to avoid recursion. std::stack point_stack; point_stack.push(projection_end); CT cur_x = projection_start; CT cur_y = parabola_y(cur_x, rot_x, rot_y); // Adjust max_dist parameter in the transformed space. const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length; while (!point_stack.empty()) { CT new_x = point_stack.top(); CT new_y = parabola_y(new_x, rot_x, rot_y); // Compute coordinates of the point of the parabola that is // furthest from the current line segment. CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x; CT mid_y = parabola_y(mid_x, rot_x, rot_y); // Compute maximum distance between the given parabolic arc // and line segment that discretize it. CT dist = (new_y - cur_y) * (mid_x - cur_x) - (new_x - cur_x) * (mid_y - cur_y); dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) + (new_x - cur_x) * (new_x - cur_x)); if (dist <= max_dist_transformed) { // Distance between parabola and line segment is less than max_dist. point_stack.pop(); CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) / sqr_segment_length + cast(x(low(segment))); CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) / sqr_segment_length + cast(y(low(segment))); discretization->push_back(Point(inter_x, inter_y)); cur_x = new_x; cur_y = new_y; } else { point_stack.push(mid_x); } } // Update last point. discretization->back() = last_point; } private: // Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b). static CT parabola_y(CT x, CT a, CT b) { return ((x - a) * (x - a) + b * b) / (b + b); } // Get normalized length of the distance between: // 1) point projection onto the segment // 2) start point of the segment // Return this length divided by the segment length. This is made to avoid // sqrt computation during transformation from the initial space to the // transformed one and vice versa. The assumption is made that projection of // the point lies between the start-point and endpoint of the segment. template class Point, template class Segment> static typename enable_if< typename gtl_and< typename gtl_if< typename is_point_concept< typename geometry_concept< Point >::type >::type >::type, typename gtl_if< typename is_segment_concept< typename geometry_concept< Segment >::type >::type >::type >::type, CT >::type get_point_projection( const Point& point, const Segment& segment) { CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment))); CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment))); CT point_vec_x = x(point) - cast(x(low(segment))); CT point_vec_y = y(point) - cast(y(low(segment))); CT sqr_segment_length = segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y; CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y; return vec_dot / sqr_segment_length; } template static CT cast(const InCT& value) { return static_cast(value); } }; } } // namespace boost::polygon #endif using namespace boost::polygon; // provides also high() and low() namespace Slic3r { namespace Geometry { static bool sort_points (Point a, Point b) { return (a.x < b.x) || (a.x == b.x && a.y < b.y); } /* This implementation is based on Andrew's monotone chain 2D convex hull algorithm */ Polygon convex_hull(Points points) { assert(points.size() >= 3); // sort input points std::sort(points.begin(), points.end(), sort_points); int n = points.size(), k = 0; Polygon hull; hull.points.resize(2*n); // Build lower hull for (int i = 0; i < n; i++) { while (k >= 2 && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--; hull.points[k++] = points[i]; } // Build upper hull for (int i = n-2, t = k+1; i >= 0; i--) { while (k >= t && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--; hull.points[k++] = points[i]; } hull.points.resize(k); assert( hull.points.front().coincides_with(hull.points.back()) ); hull.points.pop_back(); return hull; } Polygon convex_hull(const Polygons &polygons) { Points pp; for (Polygons::const_iterator p = polygons.begin(); p != polygons.end(); ++p) { pp.insert(pp.end(), p->points.begin(), p->points.end()); } return convex_hull(pp); } /* accepts an arrayref of points and returns a list of indices according to a nearest-neighbor walk */ void chained_path(const Points &points, std::vector &retval, Point start_near) { PointConstPtrs my_points; std::map indices; my_points.reserve(points.size()); for (Points::const_iterator it = points.begin(); it != points.end(); ++it) { my_points.push_back(&*it); indices[&*it] = it - points.begin(); } retval.reserve(points.size()); while (!my_points.empty()) { Points::size_type idx = start_near.nearest_point_index(my_points); start_near = *my_points[idx]; retval.push_back(indices[ my_points[idx] ]); my_points.erase(my_points.begin() + idx); } } void chained_path(const Points &points, std::vector &retval) { if (points.empty()) return; // can't call front() on empty vector chained_path(points, retval, points.front()); } /* retval and items must be different containers */ template void chained_path_items(Points &points, T &items, T &retval) { std::vector indices; chained_path(points, indices); for (std::vector::const_iterator it = indices.begin(); it != indices.end(); ++it) retval.push_back(items[*it]); } template void chained_path_items(Points &points, ClipperLib::PolyNodes &items, ClipperLib::PolyNodes &retval); bool directions_parallel(double angle1, double angle2, double max_diff) { double diff = fabs(angle1 - angle2); max_diff += EPSILON; return diff < max_diff || fabs(diff - PI) < max_diff; } template bool contains(const std::vector &vector, const Point &point) { for (const T &it : vector) if (it.contains(point)) return true; return false; } template bool contains(const Polygons &vector, const Point &point); template bool contains(const ExPolygons &vector, const Point &point); template double area(const std::vector &vector) { double area = 0; for (const T &it : vector) area += it.area(); return area; } template double area(const Polygons &vector); double rad2deg(double angle) { return angle / PI * 180.0; } double rad2deg_dir(double angle) { angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0); if (angle < 0) angle += PI; return rad2deg(angle); } double deg2rad(double angle) { return PI * angle / 180.0; } double linint(double value, double oldmin, double oldmax, double newmin, double newmax) { return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin; } /* == Perl implementations for methods tested in geometry.t but not translated. == == The first three are unreachable in the current perl code and the fourth is == == only called from ArcFitting which currently dies before reaching the call. == sub point_in_segment { my ($point, $line) = @_; my ($x, $y) = @$point; my $line_p = $line->pp; my @line_x = sort { $a <=> $b } $line_p->[A][X], $line_p->[B][X]; my @line_y = sort { $a <=> $b } $line_p->[A][Y], $line_p->[B][Y]; # check whether the point is in the segment bounding box return 0 unless $x >= ($line_x[0] - epsilon) && $x <= ($line_x[1] + epsilon) && $y >= ($line_y[0] - epsilon) && $y <= ($line_y[1] + epsilon); # if line is vertical, check whether point's X is the same as the line if ($line_p->[A][X] == $line_p->[B][X]) { return abs($x - $line_p->[A][X]) < epsilon ? 1 : 0; } # calculate the Y in line at X of the point my $y3 = $line_p->[A][Y] + ($line_p->[B][Y] - $line_p->[A][Y]) * ($x - $line_p->[A][X]) / ($line_p->[B][X] - $line_p->[A][X]); return abs($y3 - $y) < epsilon ? 1 : 0; } # given a $polygon, return the (first) segment having $point sub polygon_segment_having_point { my ($polygon, $point) = @_; foreach my $line (@{ $polygon->lines }) { return $line if point_in_segment($point, $line); } return undef; } # polygon must be simple (non complex) and ccw sub polygon_is_convex { my ($points) = @_; for (my $i = 0; $i <= $#$points; $i++) { my $angle = angle3points($points->[$i-1], $points->[$i-2], $points->[$i]); return 0 if $angle < PI; } return 1; } # this assumes a CCW rotation from $p2 to $p3 around $p1 sub angle3points { my ($p1, $p2, $p3) = @_; # p1 is the center my $angle = atan2($p2->[X] - $p1->[X], $p2->[Y] - $p1->[Y]) - atan2($p3->[X] - $p1->[X], $p3->[Y] - $p1->[Y]); # we only want to return only positive angles return $angle <= 0 ? $angle + 2*PI() : $angle; } */ class ArrangeItem { public: Pointf pos; size_t index_x, index_y; coordf_t dist; }; class ArrangeItemIndex { public: coordf_t index; ArrangeItem item; ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {}; }; bool arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const BoundingBoxf* bb, Pointfs &positions) { positions.clear(); Pointf part = part_size; // use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm part.x += dist; part.y += dist; Pointf area; if (bb != NULL && bb->defined) { area = bb->size(); } else { // bogus area size, large enough not to trigger the error below area.x = part.x * total_parts; area.y = part.y * total_parts; } // this is how many cells we have available into which to put parts size_t cellw = floor((area.x + dist) / part.x); size_t cellh = floor((area.y + dist) / part.y); if (total_parts > (cellw * cellh)) return false; // total space used by cells Pointf cells(cellw * part.x, cellh * part.y); // bounding box of total space used by cells BoundingBoxf cells_bb; cells_bb.merge(Pointf(0,0)); // min cells_bb.merge(cells); // max // center bounding box to area cells_bb.translate( (area.x - cells.x) / 2, (area.y - cells.y) / 2 ); // list of cells, sorted by distance from center std::vector cellsorder; // work out distance for all cells, sort into list for (size_t i = 0; i <= cellw-1; ++i) { for (size_t j = 0; j <= cellh-1; ++j) { coordf_t cx = linint(i + 0.5, 0, cellw, cells_bb.min.x, cells_bb.max.x); coordf_t cy = linint(j + 0.5, 0, cellh, cells_bb.min.y, cells_bb.max.y); coordf_t xd = fabs((area.x / 2) - cx); coordf_t yd = fabs((area.y / 2) - cy); ArrangeItem c; c.pos.x = cx; c.pos.y = cy; c.index_x = i; c.index_y = j; c.dist = xd * xd + yd * yd - fabs((cellw / 2) - (i + 0.5)); // binary insertion sort { coordf_t index = c.dist; size_t low = 0; size_t high = cellsorder.size(); while (low < high) { size_t mid = (low + ((high - low) / 2)) | 0; coordf_t midval = cellsorder[mid].index; if (midval < index) { low = mid + 1; } else if (midval > index) { high = mid; } else { cellsorder.insert(cellsorder.begin() + mid, ArrangeItemIndex(index, c)); goto ENDSORT; } } cellsorder.insert(cellsorder.begin() + low, ArrangeItemIndex(index, c)); } ENDSORT: ; } } // the extents of cells actually used by objects coordf_t lx = 0; coordf_t ty = 0; coordf_t rx = 0; coordf_t by = 0; // now find cells actually used by objects, map out the extents so we can position correctly for (size_t i = 1; i <= total_parts; ++i) { ArrangeItemIndex c = cellsorder[i - 1]; coordf_t cx = c.item.index_x; coordf_t cy = c.item.index_y; if (i == 1) { lx = rx = cx; ty = by = cy; } else { if (cx > rx) rx = cx; if (cx < lx) lx = cx; if (cy > by) by = cy; if (cy < ty) ty = cy; } } // now we actually place objects into cells, positioned such that the left and bottom borders are at 0 for (size_t i = 1; i <= total_parts; ++i) { ArrangeItemIndex c = cellsorder.front(); cellsorder.erase(cellsorder.begin()); coordf_t cx = c.item.index_x - lx; coordf_t cy = c.item.index_y - ty; positions.push_back(Pointf(cx * part.x, cy * part.y)); } if (bb != NULL && bb->defined) { for (Pointfs::iterator p = positions.begin(); p != positions.end(); ++p) { p->x += bb->min.x; p->y += bb->min.y; } } return true; } void MedialAxis::build(ThickPolylines* polylines) { construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd); /* // DEBUG: dump all Voronoi edges { SVG svg("voronoi.svg"); svg.draw(*this->expolygon); for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { if (edge->is_infinite()) continue; ThickPolyline polyline; polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); polyline.width.push_back(this->max_width); polyline.width.push_back(this->max_width); polylines->push_back(polyline); svg.draw(polyline); } svg.Close(); return; } */ // collect valid edges (i.e. prune those not belonging to MAT) // note: this keeps twins, so it inserts twice the number of the valid edges this->valid_edges.clear(); { std::set seen_edges; for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { // if we only process segments representing closed loops, none if the // infinite edges (if any) would be part of our MAT anyway if (edge->is_secondary() || edge->is_infinite()) continue; // don't re-validate twins if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed? seen_edges.insert(&*edge); seen_edges.insert(edge->twin()); if (!this->validate_edge(&*edge)) continue; this->valid_edges.insert(&*edge); this->valid_edges.insert(edge->twin()); } } this->edges = this->valid_edges; // iterate through the valid edges to build polylines while (!this->edges.empty()) { const VD::edge_type* edge = *this->edges.begin(); // start a polyline ThickPolyline polyline; polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); polyline.width.push_back(this->thickness[edge].first); polyline.width.push_back(this->thickness[edge].second); // remove this edge and its twin from the available edges (void)this->edges.erase(edge); (void)this->edges.erase(edge->twin()); // get next points this->process_edge_neighbors(edge, &polyline); // get previous points { ThickPolyline rpolyline; this->process_edge_neighbors(edge->twin(), &rpolyline); polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend()); polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend()); polyline.endpoints.first = rpolyline.endpoints.second; } assert(polyline.width.size() == polyline.points.size()*2 - 2); // prevent loop endpoints from being extended if (polyline.first_point().coincides_with(polyline.last_point())) { polyline.endpoints.first = false; polyline.endpoints.second = false; } // append polyline to result polylines->push_back(polyline); } } void MedialAxis::build(Polylines* polylines) { ThickPolylines tp; this->build(&tp); polylines->insert(polylines->end(), tp.begin(), tp.end()); } void MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline) { while (true) { // Since rot_next() works on the edge starting point but we want // to find neighbors on the ending point, we just swap edge with // its twin. const VD::edge_type* twin = edge->twin(); // count neighbors for this edge std::vector neighbors; for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin; neighbor = neighbor->rot_next()) { if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor); } // if we have a single neighbor then we can continue recursively if (neighbors.size() == 1) { const VD::edge_type* neighbor = neighbors.front(); // break if this is a closed loop if (this->edges.count(neighbor) == 0) return; Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y()); polyline->points.push_back(new_point); polyline->width.push_back(this->thickness[neighbor].first); polyline->width.push_back(this->thickness[neighbor].second); (void)this->edges.erase(neighbor); (void)this->edges.erase(neighbor->twin()); edge = neighbor; } else if (neighbors.size() == 0) { polyline->endpoints.second = true; return; } else { // T-shaped or star-shaped joint return; } } } bool MedialAxis::validate_edge(const VD::edge_type* edge) { // prevent overflows and detect almost-infinite edges if (std::abs(edge->vertex0()->x()) > (double)MAX_COORD || std::abs(edge->vertex0()->y()) > (double)MAX_COORD || std::abs(edge->vertex1()->x()) > (double)MAX_COORD || std::abs(edge->vertex1()->y()) > (double)MAX_COORD) return false; // construct the line representing this edge of the Voronoi diagram const Line line( Point( edge->vertex0()->x(), edge->vertex0()->y() ), Point( edge->vertex1()->x(), edge->vertex1()->y() ) ); // discard edge if it lies outside the supplied shape // this could maybe be optimized (checking inclusion of the endpoints // might give false positives as they might belong to the contour itself) if (this->expolygon != NULL) { if (line.a.coincides_with(line.b)) { // in this case, contains(line) returns a false positive if (!this->expolygon->contains(line.a)) return false; } else { if (!this->expolygon->contains(line)) return false; } } // retrieve the original line segments which generated the edge we're checking const VD::cell_type* cell_l = edge->cell(); const VD::cell_type* cell_r = edge->twin()->cell(); const Line &segment_l = this->retrieve_segment(cell_l); const Line &segment_r = this->retrieve_segment(cell_r); /* SVG svg("edge.svg"); svg.draw(*this->expolygon); svg.draw(line); svg.draw(segment_l, "red"); svg.draw(segment_r, "blue"); svg.Close(); */ /* Calculate thickness of the cross-section at both the endpoints of this edge. Our Voronoi edge is part of a CCW sequence going around its Voronoi cell located on the left side. (segment_l). This edge's twin goes around segment_r. Thus, segment_r is oriented in the same direction as our main edge, and segment_l is oriented in the same direction as our twin edge. We used to only consider the (half-)distances to segment_r, and that works whenever segment_l and segment_r are almost specular and facing. However, at curves they are staggered and they only face for a very little length (our very short edge represents such visibility). Both w0 and w1 can be calculated either towards cell_l or cell_r with equal results by Voronoi definition. When cell_l or cell_r don't refer to the segment but only to an endpoint, we calculate the distance to that endpoint instead. */ coordf_t w0 = cell_r->contains_segment() ? line.a.distance_to(segment_r)*2 : line.a.distance_to(this->retrieve_endpoint(cell_r))*2; coordf_t w1 = cell_l->contains_segment() ? line.b.distance_to(segment_l)*2 : line.b.distance_to(this->retrieve_endpoint(cell_l))*2; if (cell_l->contains_segment() && cell_r->contains_segment()) { // calculate the relative angle between the two boundary segments double angle = fabs(segment_r.orientation() - segment_l.orientation()); if (angle > PI) angle = 2*PI - angle; assert(angle >= 0 && angle <= PI); // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) // we're interested only in segments close to the second case (facing segments) // so we allow some tolerance. // this filter ensures that we're dealing with a narrow/oriented area (longer than thick) // we don't run it on edges not generated by two segments (thus generated by one segment // and the endpoint of another segment), since their orientation would not be meaningful if (PI - angle > PI/8) { // angle is not narrow enough // only apply this filter to segments that are not too short otherwise their // angle could possibly be not meaningful if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width) return false; } } else { if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON) return false; } if (w0 < this->min_width && w1 < this->min_width) return false; if (w0 > this->max_width && w1 > this->max_width) return false; this->thickness[edge] = std::make_pair(w0, w1); this->thickness[edge->twin()] = std::make_pair(w1, w0); return true; } const Line& MedialAxis::retrieve_segment(const VD::cell_type* cell) const { return this->lines[cell->source_index()]; } const Point& MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const { const Line& line = this->retrieve_segment(cell); if (cell->source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) { return line.a; } else { return line.b; } } } }