1 //Copyright (c) 2019 Ultimaker B.V.
2 //CuraEngine is released under the terms of the AGPLv3 or higher.
3 
4 #include "polygon.h"
5 
6 #include "linearAlg2D.h" // pointLiesOnTheRightOfLine
7 
8 #include "ListPolyIt.h"
9 
10 namespace cura
11 {
12 
size() const13 size_t ConstPolygonRef::size() const
14 {
15     return path->size();
16 }
17 
empty() const18 bool ConstPolygonRef::empty() const
19 {
20     return path->empty();
21 }
22 
shorterThan(const coord_t check_length) const23 bool ConstPolygonRef::shorterThan(const coord_t check_length) const
24 {
25     const ConstPolygonRef& polygon = *this;
26     const Point* p0 = &polygon.back();
27     int64_t length = 0;
28     for (const Point& p1 : polygon)
29     {
30         length += vSize(*p0 - p1);
31         if (length >= check_length)
32         {
33             return false;
34         }
35         p0 = &p1;
36     }
37     return true;
38 }
39 
_inside(Point p,bool border_result) const40 bool ConstPolygonRef::_inside(Point p, bool border_result) const
41 {
42     const ConstPolygonRef thiss = *this;
43     if (size() < 1)
44     {
45         return false;
46     }
47 
48     int crossings = 0;
49     Point p0 = back();
50     for(unsigned int n=0; n<size(); n++)
51     {
52         Point p1 = thiss[n];
53         // no tests unless the segment p0-p1 is at least partly at, or to right of, p.X
54         short comp = LinearAlg2D::pointLiesOnTheRightOfLine(p, p0, p1);
55         if (comp == 1)
56         {
57             crossings++;
58         }
59         else if (comp == 0)
60         {
61             return border_result;
62         }
63         p0 = p1;
64     }
65     return (crossings % 2) == 1;
66 }
67 
68 
intersection(const ConstPolygonRef & other) const69 Polygons ConstPolygonRef::intersection(const ConstPolygonRef& other) const
70 {
71     Polygons ret;
72     ClipperLib::Clipper clipper(clipper_init);
73     clipper.AddPath(*path, ClipperLib::ptSubject, true);
74     clipper.AddPath(*other.path, ClipperLib::ptClip, true);
75     clipper.Execute(ClipperLib::ctIntersection, ret.paths);
76     return ret;
77 }
78 
empty() const79 bool Polygons::empty() const
80 {
81     return paths.empty();
82 }
83 
approxConvexHull(int extra_outset)84 Polygons Polygons::approxConvexHull(int extra_outset)
85 {
86     constexpr int overshoot = 100000; //10cm (hard-coded value).
87 
88     Polygons convex_hull;
89     //Perform the offset for each polygon one at a time.
90     //This is necessary because the polygons may overlap, in which case the offset could end up in an infinite loop.
91     //See http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/_Body.htm
92     for (const ClipperLib::Path path : paths)
93     {
94         Polygons offset_result;
95         ClipperLib::ClipperOffset offsetter(1.2, 10.0);
96         offsetter.AddPath(path, ClipperLib::jtRound, ClipperLib::etClosedPolygon);
97         offsetter.Execute(offset_result.paths, overshoot);
98         convex_hull.add(offset_result);
99     }
100     return convex_hull.unionPolygons().offset(-overshoot + extra_outset, ClipperLib::jtRound);
101 }
102 
pointCount() const103 unsigned int Polygons::pointCount() const
104 {
105     unsigned int count = 0;
106     for (const ClipperLib::Path& path : paths)
107     {
108         count += path.size();
109     }
110     return count;
111 }
112 
inside(Point p,bool border_result) const113 bool Polygons::inside(Point p, bool border_result) const
114 {
115     int poly_count_inside = 0;
116     for (const ClipperLib::Path& poly : *this)
117     {
118         const int is_inside_this_poly = ClipperLib::PointInPolygon(p, poly);
119         if (is_inside_this_poly == -1)
120         {
121             return border_result;
122         }
123         poly_count_inside += is_inside_this_poly;
124     }
125     return (poly_count_inside % 2) == 1;
126 }
127 
inside(Point p,bool border_result) const128 bool PolygonsPart::inside(Point p, bool border_result) const
129 {
130     if (size() < 1)
131     {
132         return false;
133     }
134     if (!(*this)[0].inside(p, border_result))
135     {
136         return false;
137     }
138     for(unsigned int n = 1; n < paths.size(); n++)
139     {
140         if ((*this)[n].inside(p, border_result))
141         {
142             return false;
143         }
144     }
145     return true;
146 }
147 
insideOld(Point p,bool border_result) const148 bool Polygons::insideOld(Point p, bool border_result) const
149 {
150     const Polygons& thiss = *this;
151     if (size() < 1)
152     {
153         return false;
154     }
155 
156     int crossings = 0;
157     for (const ClipperLib::Path& poly : thiss)
158     {
159         Point p0 = poly.back();
160         for (const Point& p1 : poly)
161         {
162             short comp = LinearAlg2D::pointLiesOnTheRightOfLine(p, p0, p1);
163             if (comp == 1)
164             {
165                 crossings++;
166             }
167             else if (comp == 0)
168             {
169                 return border_result;
170             }
171             p0 = p1;
172         }
173     }
174     return (crossings % 2) == 1;
175 }
176 
findInside(Point p,bool border_result)177 unsigned int Polygons::findInside(Point p, bool border_result)
178 {
179     Polygons& thiss = *this;
180     if (size() < 1)
181     {
182         return false;
183     }
184 
185     // NOTE: Keep these vectors fixed-size, they replace an (non-standard, sized at runtime) arrays.
186     std::vector<int64_t> min_x(size(), std::numeric_limits<int64_t>::max());
187     std::vector<int64_t> crossings(size());
188 
189     for (unsigned int poly_idx = 0; poly_idx < size(); poly_idx++)
190     {
191         PolygonRef poly = thiss[poly_idx];
192         Point p0 = poly.back();
193         for(Point& p1 : poly)
194         {
195             short comp = LinearAlg2D::pointLiesOnTheRightOfLine(p, p0, p1);
196             if (comp == 1)
197             {
198                 crossings[poly_idx]++;
199                 int64_t x;
200                 if (p1.Y == p0.Y)
201                 {
202                     x = p0.X;
203                 }
204                 else
205                 {
206                     x = p0.X + (p1.X-p0.X) * (p.Y-p0.Y) / (p1.Y-p0.Y);
207                 }
208                 if (x < min_x[poly_idx])
209                 {
210                     min_x[poly_idx] = x;
211                 }
212             }
213             else if (border_result && comp == 0)
214             {
215                 return poly_idx;
216             }
217             p0 = p1;
218         }
219     }
220 
221     int64_t min_x_uneven = std::numeric_limits<int64_t>::max();
222     unsigned int ret = NO_INDEX;
223     unsigned int n_unevens = 0;
224     for (unsigned int array_idx = 0; array_idx < size(); array_idx++)
225     {
226         if (crossings[array_idx] % 2 == 1)
227         {
228             n_unevens++;
229             if (min_x[array_idx] < min_x_uneven)
230             {
231                 min_x_uneven = min_x[array_idx];
232                 ret = array_idx;
233             }
234         }
235     }
236     if (n_unevens % 2 == 0) { ret = NO_INDEX; }
237     return ret;
238 }
239 
intersectionPolyLines(const Polygons & polylines) const240 Polygons Polygons::intersectionPolyLines(const Polygons& polylines) const
241 {
242     ClipperLib::PolyTree result;
243     ClipperLib::Clipper clipper(clipper_init);
244     clipper.AddPaths(polylines.paths, ClipperLib::ptSubject, false);
245     clipper.AddPaths(paths, ClipperLib::ptClip, true);
246     clipper.Execute(ClipperLib::ctIntersection, result);
247     Polygons ret;
248     ret.addPolyTreeNodeRecursive(result);
249     return ret;
250 }
251 
polyLineLength() const252 coord_t Polygons::polyLineLength() const
253 {
254     coord_t length = 0;
255     for (unsigned int poly_idx = 0; poly_idx < paths.size(); poly_idx++)
256     {
257         Point p0 = paths[poly_idx][0];
258         for (unsigned int point_idx = 1; point_idx < paths[poly_idx].size(); point_idx++)
259         {
260             Point p1 = paths[poly_idx][point_idx];
261             length += vSize(p0 - p1);
262             p0 = p1;
263         }
264     }
265     return length;
266 }
267 
offset(int distance,ClipperLib::JoinType join_type,double miter_limit) const268 Polygons Polygons::offset(int distance, ClipperLib::JoinType join_type, double miter_limit) const
269 {
270     if (distance == 0)
271     {
272         return *this;
273     }
274     Polygons ret;
275     ClipperLib::ClipperOffset clipper(miter_limit, 10.0);
276     clipper.AddPaths(unionPolygons().paths, join_type, ClipperLib::etClosedPolygon);
277     clipper.MiterLimit = miter_limit;
278     clipper.Execute(ret.paths, distance);
279     return ret;
280 }
281 
offset(int distance,ClipperLib::JoinType join_type,double miter_limit) const282 Polygons ConstPolygonRef::offset(int distance, ClipperLib::JoinType join_type, double miter_limit) const
283 {
284     if (distance == 0)
285     {
286         Polygons ret;
287         ret.add(*this);
288         return ret;
289     }
290     Polygons ret;
291     ClipperLib::ClipperOffset clipper(miter_limit, 10.0);
292     clipper.AddPath(*path, join_type, ClipperLib::etClosedPolygon);
293     clipper.MiterLimit = miter_limit;
294     clipper.Execute(ret.paths, distance);
295     return ret;
296 }
297 
simplify(const coord_t smallest_line_segment_squared,const coord_t allowed_error_distance_squared)298 void PolygonRef::simplify(const coord_t smallest_line_segment_squared, const coord_t allowed_error_distance_squared)
299 {
300     if (size() < 3)
301     {
302         clear();
303         return;
304     }
305     if (size() == 3)
306     {
307         return;
308     }
309 
310     ClipperLib::Path new_path;
311     Point previous = path->back();
312     Point current = path->at(0);
313 
314     /* When removing a vertex, we check the height of the triangle of the area
315      being removed from the original polygon by the simplification. However,
316      when consecutively removing multiple vertices the height of the previously
317      removed vertices w.r.t. the shortcut path changes.
318      In order to not recompute the new height value of previously removed
319      vertices we compute the height of a representative triangle, which covers
320      the same amount of area as the area being cut off. We use the Shoelace
321      formula to accumulate the area under the removed segments. This works by
322      computing the area in a 'fan' where each of the blades of the fan go from
323      the origin to one of the segments. While removing vertices the area in
324      this fan accumulates. By subtracting the area of the blade connected to
325      the short-cutting segment we obtain the total area of the cutoff region.
326      From this area we compute the height of the representative triangle using
327      the standard formula for a triangle area: A = .5*b*h
328      */
329     coord_t accumulated_area_removed = previous.X * current.Y - previous.Y * current.X; // Twice the Shoelace formula for area of polygon per line segment.
330 
331     for (size_t point_idx = 0; point_idx < size(); point_idx++)
332     {
333         current = path->at(point_idx % size());
334 
335         //Check if the accumulated area doesn't exceed the maximum.
336         Point next;
337         if (point_idx + 1 < size())
338         {
339             next = path->at(point_idx + 1);
340         }
341         else if (point_idx + 1 == size() && new_path.size() > 1)
342         { // don't spill over if the [next] vertex will then be equal to [previous]
343             next = new_path[0]; //Spill over to new polygon for checking removed area.
344         }
345         else
346         {
347             next = path->at((point_idx + 1) % size());
348         }
349 
350         const coord_t removed_area_next = current.X * next.Y - current.Y * next.X; // Twice the Shoelace formula for area of polygon per line segment.
351         const coord_t negative_area_closing = next.X * previous.Y - next.Y * previous.X; // area between the origin and the short-cutting segment
352         accumulated_area_removed += removed_area_next;
353 
354         const coord_t length2 = vSize2(current - previous);
355         const coord_t next_length2 = vSize2(current - next);
356 
357         const coord_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // close the shortcut area polygon
358         const coord_t base_length_2 = vSize2(next - previous);
359 
360         if (base_length_2 == 0) //Two line segments form a line back and forth with no area.
361         {
362             continue; //Remove the vertex.
363         }
364         //We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared.
365         //1/2 L = A           [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5
366         //A = 1/2 * b * h     [triangle area formula]
367         //L = b * h           [apply above two and take out the 1/2]
368         //h = L / b           [divide by b]
369         //h^2 = (L / b)^2     [square it]
370         //h^2 = L^2 / b^2     [factor the divisor]
371         const coord_t height_2 = area_removed_so_far * area_removed_so_far / base_length_2;
372         if ((height_2 <= 1 //Almost exactly colinear (barring rounding errors).
373             && LinearAlg2D::getDist2FromLine(current, previous, next) <= 1) // make sure that height_2 is not small because of cancellation of positive and negative areas
374             || (length2 < smallest_line_segment_squared
375                 && next_length2 < smallest_line_segment_squared // Segments are small
376                 && height_2 <= allowed_error_distance_squared) // removing the vertex doesn't introduce too much error.
377         )
378         {
379             continue; //Remove the vertex.
380         }
381 
382         //Don't remove the vertex.
383 
384         accumulated_area_removed = removed_area_next; // so that in the next iteration it's the area between the origin, [previous] and [current]
385         previous = current; //Note that "previous" is only updated if we don't remove the vertex.
386         new_path.push_back(current);
387     }
388     *path = new_path;
389 }
390 
applyMatrix(const PointMatrix & matrix)391 void PolygonRef::applyMatrix(const PointMatrix& matrix)
392 {
393     for (unsigned int path_idx = 0; path_idx < path->size(); path_idx++)
394     {
395         (*path)[path_idx] = matrix.apply((*path)[path_idx]);
396     }
397 }
applyMatrix(const Point3Matrix & matrix)398 void PolygonRef::applyMatrix(const Point3Matrix& matrix)
399 {
400     for (unsigned int path_idx = 0; path_idx < path->size(); path_idx++)
401     {
402         (*path)[path_idx] = matrix.apply((*path)[path_idx]);
403     }
404 }
405 
getOutsidePolygons() const406 Polygons Polygons::getOutsidePolygons() const
407 {
408     Polygons ret;
409     ClipperLib::Clipper clipper(clipper_init);
410     ClipperLib::PolyTree poly_tree;
411     constexpr bool paths_are_closed_polys = true;
412     clipper.AddPaths(paths, ClipperLib::ptSubject, paths_are_closed_polys);
413     clipper.Execute(ClipperLib::ctUnion, poly_tree);
414 
415     for (int outer_poly_idx = 0; outer_poly_idx < poly_tree.ChildCount(); outer_poly_idx++)
416     {
417         ClipperLib::PolyNode* child = poly_tree.Childs[outer_poly_idx];
418         ret.emplace_back(child->Contour);
419     }
420     return ret;
421 }
422 
removeEmptyHoles() const423 Polygons Polygons::removeEmptyHoles() const
424 {
425     Polygons ret;
426     ClipperLib::Clipper clipper(clipper_init);
427     ClipperLib::PolyTree poly_tree;
428     constexpr bool paths_are_closed_polys = true;
429     clipper.AddPaths(paths, ClipperLib::ptSubject, paths_are_closed_polys);
430     clipper.Execute(ClipperLib::ctUnion, poly_tree);
431 
432     bool remove_holes = true;
433     removeEmptyHoles_processPolyTreeNode(poly_tree, remove_holes, ret);
434     return ret;
435 }
436 
getEmptyHoles() const437 Polygons Polygons::getEmptyHoles() const
438 {
439     Polygons ret;
440     ClipperLib::Clipper clipper(clipper_init);
441     ClipperLib::PolyTree poly_tree;
442     constexpr bool paths_are_closed_polys = true;
443     clipper.AddPaths(paths, ClipperLib::ptSubject, paths_are_closed_polys);
444     clipper.Execute(ClipperLib::ctUnion, poly_tree);
445 
446     bool remove_holes = false;
447     removeEmptyHoles_processPolyTreeNode(poly_tree, remove_holes, ret);
448     return ret;
449 }
450 
removeEmptyHoles_processPolyTreeNode(const ClipperLib::PolyNode & node,const bool remove_holes,Polygons & ret) const451 void Polygons::removeEmptyHoles_processPolyTreeNode(const ClipperLib::PolyNode& node, const bool remove_holes, Polygons& ret) const
452 {
453     for (int outer_poly_idx = 0; outer_poly_idx < node.ChildCount(); outer_poly_idx++)
454     {
455         ClipperLib::PolyNode* child = node.Childs[outer_poly_idx];
456         if (remove_holes)
457         {
458             ret.emplace_back(child->Contour);
459         }
460         for (int hole_node_idx = 0; hole_node_idx < child->ChildCount(); hole_node_idx++)
461         {
462             ClipperLib::PolyNode& hole_node = *child->Childs[hole_node_idx];
463             if ((hole_node.ChildCount() > 0) == remove_holes)
464             {
465                 ret.emplace_back(hole_node.Contour);
466                 removeEmptyHoles_processPolyTreeNode(hole_node, remove_holes, ret);
467             }
468         }
469     }
470 }
471 
removeSmallAreas(const double min_area_size,const bool remove_holes)472 void Polygons::removeSmallAreas(const double min_area_size, const bool remove_holes)
473 {
474     std::vector<ConstPolygonRef> outlines_removed;
475     std::vector<size_t> small_hole_indices;
476     Polygons& thiss = *this;
477     for(size_t i = 0; i < size(); i++)
478     {
479         double area = INT2MM(INT2MM(thiss[i].area()));
480         // holes have negative area and small holes will be ignored unless remove_holes is true
481         // or the hole is contained within an outline that is itself smaller in area than the threshold
482         if (fabs(area) < min_area_size)
483         {
484             if (!remove_holes)
485             {
486                 if (area > 0)
487                 {
488                     // remember this outline has been removed so we can later check if it contains any holes that also need to be removed
489                     outlines_removed.push_back(thiss[i]);
490                 }
491                 else
492                 {
493                     // remember this small hole so we can later check if it is contained within an outline that has been removed
494                     small_hole_indices.push_back(i);
495                 }
496             }
497             // the polygon area is below the threshold, remove it if it is an outline or we are removing holes as well as outlines
498             if (area > 0 || remove_holes)
499             {
500                 remove(i);
501                 i -= 1;
502             }
503         }
504     }
505     if (outlines_removed.size() > 0 && small_hole_indices.size() > 0)
506     {
507         size_t num_holes_removed = 0;
508         // now remove any holes that are inside outlines that have been removed
509         for (size_t small_hole_index : small_hole_indices)
510         {
511             const size_t hole_index = small_hole_index - num_holes_removed; // adjust index to account for removed holes
512             // if hole polygon's first point is inside a removed polygon, remove the hole polygon also
513             for (ConstPolygonRef removed_outline : outlines_removed)
514             {
515                 if (removed_outline.inside(thiss[hole_index][0]))
516                 {
517                     remove(hole_index);
518                     ++num_holes_removed;
519                     break;
520                 }
521             }
522         }
523     }
524 }
525 
toPolygons(ClipperLib::PolyTree & poly_tree)526 Polygons Polygons::toPolygons(ClipperLib::PolyTree& poly_tree)
527 {
528     Polygons ret;
529     ret.addPolyTreeNodeRecursive(poly_tree);
530     return ret;
531 }
532 
533 
addPolyTreeNodeRecursive(const ClipperLib::PolyNode & node)534 void Polygons::addPolyTreeNodeRecursive(const ClipperLib::PolyNode& node)
535 {
536     for (int outer_poly_idx = 0; outer_poly_idx < node.ChildCount(); outer_poly_idx++)
537     {
538         ClipperLib::PolyNode* child = node.Childs[outer_poly_idx];
539         paths.push_back(child->Contour);
540         addPolyTreeNodeRecursive(*child);
541     }
542 }
543 
smooth_corner_complex(const Point p1,ListPolyIt & p0_it,ListPolyIt & p2_it,const int64_t shortcut_length)544 bool ConstPolygonRef::smooth_corner_complex(const Point p1, ListPolyIt& p0_it, ListPolyIt& p2_it, const int64_t shortcut_length)
545 {
546     // walk away from the corner until the shortcut > shortcut_length or it would smooth a piece inward
547     // - walk in both directions untill shortcut > shortcut_length
548     // - stop walking in one direction if it would otherwise cut off a corner in that direction
549     // - same in the other direction
550     // - stop if both are cut off
551     // walk by updating p0_it and p2_it
552     int64_t shortcut_length2 = shortcut_length * shortcut_length;
553     bool forward_is_blocked = false;
554     bool forward_is_too_far = false;
555     bool backward_is_blocked = false;
556     bool backward_is_too_far = false;
557     while (true)
558     {
559         const bool forward_has_converged = forward_is_blocked || forward_is_too_far;
560         const bool backward_has_converged = backward_is_blocked || backward_is_too_far;
561         if (forward_has_converged && backward_has_converged)
562         {
563             if (forward_is_too_far && backward_is_too_far && vSize2(p0_it.prev().p() - p2_it.next().p()) < shortcut_length2)
564             {
565                 //         o
566                 //       /   \                                                  .
567                 //      o     o
568                 //      |     |
569                 //      \     /                                                 .
570                 //       |   |
571                 //       \   /                                                  .
572                 //        | |
573                 //        o o
574                 --p0_it;
575                 ++p2_it;
576                 forward_is_too_far = false; // invalidate data
577                 backward_is_too_far = false; // invalidate data
578                 continue;
579             }
580             else
581             {
582                 break;
583             }
584         }
585         smooth_outward_step(p1, shortcut_length2, p0_it, p2_it, forward_is_blocked, backward_is_blocked, forward_is_too_far, backward_is_too_far);
586         if (p0_it.prev() == p2_it || p0_it == p2_it)
587         { // stop if we went all the way around the polygon
588             // this should only be the case for hole polygons (?)
589             if (forward_is_too_far && backward_is_too_far)
590             {
591                 // in case p0_it.prev() == p2_it :
592                 //     /                                                .
593                 //    /                /|
594                 //   |       becomes  | |
595                 //    \                \|
596                 //     \                                                .
597                 // in case p0_it == p2_it :
598                 //     /                                                .
599                 //    /    becomes     /|
600                 //    \                \|
601                 //     \                                                .
602                 break;
603             }
604             else
605             {
606                 // this whole polygon can be removed
607                 return true;
608             }
609         }
610     }
611 
612     const Point v02 = p2_it.p() - p0_it.p();
613     const int64_t v02_size2 = vSize2(v02);
614     // set the following:
615     // p0_it = start point of line
616     // p2_it = end point of line
617     if (std::abs(v02_size2 - shortcut_length2) < shortcut_length * 10) // i.e. if (size2 < l * (l+10) && size2 > l * (l-10))
618     { // v02 is approximately shortcut length
619         // handle this separately to avoid rounding problems below in the getPointOnLineWithDist function
620         // p0_it and p2_it are already correct
621     }
622     else if (!backward_is_blocked && !forward_is_blocked)
623     { // introduce two new points
624         //  1----b---->2
625         //  ^   /
626         //  |  /
627         //  | /
628         //  |/
629         //  |a
630         //  |
631         //  0
632         const int64_t v02_size = sqrt(v02_size2);
633 
634         const ListPolyIt p0_2_it = p0_it.prev();
635         const ListPolyIt p2_2_it = p2_it.next();
636         const Point p2_2 = p2_2_it.p();
637         const Point p0_2 = p0_2_it.p();
638         const Point v02_2 = p0_2 - p2_2;
639         const int64_t v02_2_size = vSize(v02_2);
640         float progress = std::min(1.0, INT2MM(shortcut_length - v02_size) / INT2MM(v02_2_size - v02_size)); // account for rounding error when v02_2_size is approx equal to v02_size
641         assert(progress >= 0.0f && progress <= 1.0f && "shortcut length must be between last length and new length");
642         const Point new_p0 = p0_it.p() + (p0_2 - p0_it.p()) * progress;
643         p0_it = ListPolyIt::insertPointNonDuplicate(p0_2_it, p0_it, new_p0);
644         const Point new_p2 = p2_it.p() + (p2_2 - p2_it.p()) * progress;
645         p2_it = ListPolyIt::insertPointNonDuplicate(p2_it, p2_2_it, new_p2);
646     }
647     else if (!backward_is_blocked)
648     { // forward is blocked, back is open
649         //     |
650         //  1->b
651         //  ^  :
652         //  | /
653         //  0 :
654         //  |/
655         //  |a
656         //  |
657         //  0_2
658         const ListPolyIt p0_2_it = p0_it.prev();
659         const Point p0 = p0_it.p();
660         const Point p0_2 = p0_2_it.p();
661         const Point p2 = p2_it.p();
662         Point new_p0;
663         bool success = LinearAlg2D::getPointOnLineWithDist(p2, p0, p0_2, shortcut_length, new_p0);
664         // shortcut length must be possible given that last length was ok and new length is too long
665         if (success)
666         {
667 #ifdef ASSERT_INSANE_OUTPUT
668             assert(vSize(new_p0) < 400000);
669 #endif // #ifdef ASSERT_INSANE_OUTPUT
670             p0_it = ListPolyIt::insertPointNonDuplicate(p0_2_it, p0_it, new_p0);
671         }
672         else
673         { // if not then a rounding error occured
674             if (vSize(p2 - p0_2) < vSize2(p2 - p0))
675             {
676                 p0_it = p0_2_it; // start shortcut at 0
677             }
678         }
679     }
680     else if (!forward_is_blocked)
681     { // backward is blocked, front is open
682         //  1----2----b----------->2_2
683         //  ^      ,-'
684         //  |   ,-'
685         //--0.-'
686         //  a
687         const ListPolyIt p2_2_it = p2_it.next();
688         const Point p0 = p0_it.p();
689         const Point p2 = p2_it.p();
690         const Point p2_2 = p2_2_it.p();
691         Point new_p2;
692         bool success = LinearAlg2D::getPointOnLineWithDist(p0, p2, p2_2, shortcut_length, new_p2);
693         // shortcut length must be possible given that last length was ok and new length is too long
694         if (success)
695         {
696 #ifdef ASSERT_INSANE_OUTPUT
697             assert(vSize(new_p2) < 400000);
698 #endif // #ifdef ASSERT_INSANE_OUTPUT
699             p2_it = ListPolyIt::insertPointNonDuplicate(p2_it, p2_2_it, new_p2);
700         }
701         else
702         { // if not then a rounding error occured
703             if (vSize(p2_2 - p0) < vSize2(p2 - p0))
704             {
705                 p2_it = p2_2_it; // start shortcut at 0
706             }
707         }
708     }
709     else
710     {
711         //        |
712         //      __|2
713         //     | /  > shortcut cannot be of the desired length
714         //  ___|/                                                       .
715         //     0
716         // both are blocked and p0_it and p2_it are already correct
717     }
718     // delete all cut off points
719     while (p0_it.next() != p2_it)
720     {
721         p0_it.next().remove();
722     }
723     return false;
724 }
725 
smooth_outward_step(const Point p1,const int64_t shortcut_length2,ListPolyIt & p0_it,ListPolyIt & p2_it,bool & forward_is_blocked,bool & backward_is_blocked,bool & forward_is_too_far,bool & backward_is_too_far)726 void ConstPolygonRef::smooth_outward_step(const Point p1, const int64_t shortcut_length2, ListPolyIt& p0_it, ListPolyIt& p2_it, bool& forward_is_blocked, bool& backward_is_blocked, bool& forward_is_too_far, bool& backward_is_too_far)
727 {
728     const bool forward_has_converged = forward_is_blocked || forward_is_too_far;
729     const bool backward_has_converged = backward_is_blocked || backward_is_too_far;
730     const Point p0 = p0_it.p();
731     const Point p2 = p2_it.p();
732     bool walk_forward = !forward_has_converged && (backward_has_converged || (vSize2(p2 - p1) < vSize2(p0 - p1))); // whether to walk along the p1-p2 direction or in the p1-p0 direction
733 
734     if (walk_forward)
735     {
736         const ListPolyIt p2_2_it = p2_it.next();
737         const Point p2_2 = p2_2_it.p();
738         bool p2_is_left = LinearAlg2D::pointIsLeftOfLine(p2, p0, p2_2) >= 0;
739         if (!p2_is_left)
740         {
741             forward_is_blocked = true;
742             return;
743         }
744 
745         const Point v02_2 = p2_2 - p0_it.p();
746         if (vSize2(v02_2) > shortcut_length2)
747         {
748             forward_is_too_far = true;
749             return;
750         }
751 
752         p2_it = p2_2_it; // make one step in the forward direction
753         backward_is_blocked = false; // invalidate data about backward walking
754         backward_is_too_far = false;
755         return;
756     }
757     else
758     {
759         const ListPolyIt p0_2_it = p0_it.prev();
760         const Point p0_2 = p0_2_it.p();
761         bool p0_is_left = LinearAlg2D::pointIsLeftOfLine(p0, p0_2, p2) >= 0;
762         if (!p0_is_left)
763         {
764             backward_is_blocked = true;
765             return;
766         }
767 
768         const Point v02_2 = p2_it.p() - p0_2;
769         if (vSize2(v02_2) > shortcut_length2)
770         {
771             backward_is_too_far = true;
772             return;
773         }
774 
775         p0_it = p0_2_it; // make one step in the backward direction
776         forward_is_blocked = false; // invalidate data about forward walking
777         forward_is_too_far = false;
778         return;
779     }
780 }
781 
smooth_corner_simple(const Point p0,const Point p1,const Point p2,const ListPolyIt p0_it,const ListPolyIt p1_it,const ListPolyIt p2_it,const Point v10,const Point v12,const Point v02,const int64_t shortcut_length,float cos_angle)782 void ConstPolygonRef::smooth_corner_simple(const Point p0, const Point p1, const Point p2, const ListPolyIt p0_it, const ListPolyIt p1_it, const ListPolyIt p2_it, const Point v10, const Point v12, const Point v02, const int64_t shortcut_length, float cos_angle)
783 {
784     //  1----b---->2
785     //  ^   /
786     //  |  /
787     //  | /
788     //  |/
789     //  |a
790     //  |
791     //  0
792     // ideally a1_size == b1_size
793     if (vSize2(v02) <= shortcut_length * (shortcut_length + 10) // v02 is approximately shortcut length
794         || (cos_angle > 0.9999 && LinearAlg2D::getDist2FromLine(p2, p0, p1) < 20 * 20)) // p1 is degenerate
795     {
796         // handle this separately to avoid rounding problems below in the getPointOnLineWithDist function
797         p1_it.remove();
798         // don't insert new elements
799     }
800     else
801     {
802         // compute the distance a1 == b1 to get vSize(ab)==shortcut_length with the given angle between v10 and v12
803         //       1
804         //      /|\                                                      .
805         //     / | \                                                     .
806         //    /  |  \                                                    .
807         //   /   |   \                                                   .
808         // a/____|____\b                                                 .
809         //       m
810         // use trigonometry on the right-angled triangle am1
811         double a1m_angle = acos(cos_angle) / 2;
812         const int64_t a1_size = shortcut_length / 2 / sin(a1m_angle);
813         if (a1_size * a1_size < vSize2(v10) && a1_size * a1_size < vSize2(v12))
814         {
815             Point a = p1 + normal(v10, a1_size);
816             Point b = p1 + normal(v12, a1_size);
817 #ifdef ASSERT_INSANE_OUTPUT
818             assert(vSize(a) < 4000000);
819             assert(vSize(b) < 4000000);
820 #endif // #ifdef ASSERT_INSANE_OUTPUT
821             ListPolyIt::insertPointNonDuplicate(p0_it, p1_it, a);
822             ListPolyIt::insertPointNonDuplicate(p1_it, p2_it, b);
823             p1_it.remove();
824         }
825         else if (vSize2(v12) < vSize2(v10))
826         {
827             //     b
828             //  1->2
829             //  ^  |
830             //  | /
831             //  | |
832             //  |/
833             //  |a
834             //  |
835             //  0
836             const Point& b = p2_it.p();
837             Point a;
838             bool success = LinearAlg2D::getPointOnLineWithDist(b, p1, p0, shortcut_length, a);
839             // v02 has to be longer than ab!
840             if (success)
841             { // if not success then assume a is negligibly close to 0, but rounding errors caused a problem
842 #ifdef ASSERT_INSANE_OUTPUT
843                 assert(vSize(a) < 4000000);
844 #endif // #ifdef ASSERT_INSANE_OUTPUT
845                 ListPolyIt::insertPointNonDuplicate(p0_it, p1_it, a);
846             }
847             p1_it.remove();
848         }
849         else
850         {
851             //  1---------b----------->2
852             //  ^      ,-'
853             //  |   ,-'
854             //  0.-'
855             //  a
856             const Point& a = p0_it.p();
857             Point b;
858             bool success = LinearAlg2D::getPointOnLineWithDist(a, p1, p2, shortcut_length, b);
859             // v02 has to be longer than ab!
860             if (success)
861             { // if not success then assume b is negligibly close to 2, but rounding errors caused a problem
862 #ifdef ASSERT_INSANE_OUTPUT
863                 assert(vSize(b) < 4000000);
864 #endif // #ifdef ASSERT_INSANE_OUTPUT
865                 ListPolyIt::insertPointNonDuplicate(p1_it, p2_it, b);
866             }
867             p1_it.remove();
868         }
869     }
870 }
871 
smooth_outward(const AngleDegrees min_angle,int shortcut_length,PolygonRef result) const872 void ConstPolygonRef::smooth_outward(const AngleDegrees min_angle, int shortcut_length, PolygonRef result) const
873 {
874 // example of smoothed out corner:
875 //
876 //               6
877 //               ^
878 //               |
879 // inside        |     outside
880 //         2>3>4>5
881 //         ^    /                   .
882 //         |   /                    .
883 //         1  /                     .
884 //         ^ /                      .
885 //         |/                       .
886 //         |
887 //         |
888 //         0
889 
890     int shortcut_length2 = shortcut_length * shortcut_length;
891     float cos_min_angle = cos(min_angle / 180 * M_PI);
892 
893     ListPolygon poly;
894     ListPolyIt::convertPolygonToList(*this, poly);
895 
896     { // remove duplicate vertices
897         ListPolyIt p1_it(poly, poly.begin());
898         do
899         {
900             ListPolyIt next = p1_it.next();
901             if (vSize2(p1_it.p() - next.p()) < 10 * 10)
902             {
903                 p1_it.remove();
904             }
905             p1_it = next;
906         } while (p1_it != ListPolyIt(poly, poly.begin()));
907     }
908 
909     ListPolyIt p1_it(poly, poly.begin());
910     do
911     {
912         const Point p1 = p1_it.p();
913         ListPolyIt p0_it = p1_it.prev();
914         ListPolyIt p2_it = p1_it.next();
915         const Point p0 = p0_it.p();
916         const Point p2 = p2_it.p();
917 
918         const Point v10 = p0 - p1;
919         const Point v12 = p2 - p1;
920         float cos_angle = INT2MM(INT2MM(dot(v10, v12))) / vSizeMM(v10) / vSizeMM(v12);
921         bool is_left_angle = LinearAlg2D::pointIsLeftOfLine(p1, p0, p2) > 0;
922         if (cos_angle > cos_min_angle && is_left_angle)
923         {
924             // angle is so sharp that it can be removed
925             Point v02 = p2_it.p() - p0_it.p();
926             if (vSize2(v02) >= shortcut_length2)
927             {
928                 smooth_corner_simple(p0, p1, p2, p0_it, p1_it, p2_it, v10, v12, v02, shortcut_length, cos_angle);
929             }
930             else
931             {
932                 bool remove_poly = smooth_corner_complex(p1, p0_it, p2_it, shortcut_length); // edits p0_it and p2_it!
933                 if (remove_poly)
934                 {
935                     // don't convert ListPolygon into result
936                     return;
937                 }
938             }
939             // update:
940             p1_it = p2_it; // next point to consider for whether it's an internal corner
941         }
942         else
943         {
944             ++p1_it;
945         }
946     } while (p1_it != ListPolyIt(poly, poly.begin()));
947 
948     ListPolyIt::convertListPolygonToPolygon(poly, result);
949 }
950 
smooth_outward(const AngleDegrees max_angle,int shortcut_length)951 Polygons Polygons::smooth_outward(const AngleDegrees max_angle, int shortcut_length)
952 {
953     Polygons ret;
954     for (unsigned int p = 0; p < size(); p++)
955     {
956         PolygonRef poly(paths[p]);
957         if (poly.size() < 3)
958         {
959             continue;
960         }
961         if (poly.size() == 3)
962         {
963             ret.add(poly);
964             continue;
965         }
966         poly.smooth_outward(max_angle, shortcut_length, ret.newPoly());
967         if (ret.back().size() < 3)
968         {
969             ret.paths.resize(ret.paths.size() - 1);
970         }
971     }
972     return ret;
973 }
974 
975 
smooth(int remove_length,PolygonRef result) const976 void ConstPolygonRef::smooth(int remove_length, PolygonRef result) const
977 {
978 // a typical zigzag with the middle part to be removed by removing (1) :
979 //
980 //               3
981 //               ^
982 //               |
983 //               |
984 // inside        |     outside
985 //          1--->2
986 //          ^
987 //          |
988 //          |
989 //          |
990 //          0
991     const ConstPolygonRef& thiss = *path;
992     ClipperLib::Path* poly = result.path;
993     if (size() > 0)
994     {
995         poly->push_back(thiss[0]);
996     }
997     auto is_zigzag = [remove_length](const int64_t v02_size, const int64_t v12_size, const int64_t v13_size, const int64_t dot1, const int64_t dot2)
998     {
999         if (v12_size > remove_length)
1000         { // v12 or v13 is too long
1001             return false;
1002         }
1003         const bool p1_is_left_of_v02 = dot1 < 0;
1004         if (!p1_is_left_of_v02)
1005         { // removing p1 wouldn't smooth outward
1006             return false;
1007         }
1008         const bool p2_is_left_of_v13 = dot2 > 0;
1009         if (p2_is_left_of_v13)
1010         { // l0123 doesn't constitute a zigzag ''|,,
1011             return false;
1012         }
1013         if (-dot1 <= v02_size * v12_size / 2)
1014         { // angle at p1 isn't sharp enough
1015             return false;
1016         }
1017         if (-dot2 <= v13_size * v12_size / 2)
1018         { // angle at p2 isn't sharp enough
1019             return false;
1020         }
1021         return true;
1022     };
1023     Point v02 = thiss[2] - thiss[0];
1024     Point v02T = turn90CCW(v02);
1025     int64_t v02_size = vSize(v02);
1026     bool force_push = false;
1027     for (unsigned int poly_idx = 1; poly_idx < size(); poly_idx++)
1028     {
1029         const Point& p1 = thiss[poly_idx];
1030         const Point& p2 = thiss[(poly_idx + 1) % size()];
1031         const Point& p3 = thiss[(poly_idx + 2) % size()];
1032         // v02 computed in last iteration
1033         // v02_size as well
1034         const Point v12 = p2 - p1;
1035         const int64_t v12_size = vSize(v12);
1036         const Point v13 = p3 - p1;
1037         const int64_t v13_size = vSize(v13);
1038 
1039         // v02T computed in last iteration
1040         const int64_t dot1 = dot(v02T, v12);
1041         const Point v13T = turn90CCW(v13);
1042         const int64_t dot2 = dot(v13T, v12);
1043         bool push_point = force_push || !is_zigzag(v02_size, v12_size, v13_size, dot1, dot2);
1044         force_push = false;
1045         if (push_point)
1046         {
1047             poly->push_back(p1);
1048         }
1049         else
1050         {
1051             // do not add the current one to the result
1052             force_push = true; // ensure the next point is added; it cannot also be a zigzag
1053         }
1054         v02T = v13T;
1055         v02 = v13;
1056         v02_size = v13_size;
1057     }
1058 }
1059 
smooth(int remove_length) const1060 Polygons Polygons::smooth(int remove_length) const
1061 {
1062     Polygons ret;
1063     for (unsigned int p = 0; p < size(); p++)
1064     {
1065         ConstPolygonRef poly(paths[p]);
1066         if (poly.size() < 3)
1067         {
1068             continue;
1069         }
1070         if (poly.size() == 3)
1071         {
1072             ret.add(poly);
1073             continue;
1074         }
1075         poly.smooth(remove_length, ret.newPoly());
1076         PolygonRef back = ret.back();
1077         if (back.size() < 3)
1078         {
1079             back.path->resize(back.path->size() - 1);
1080         }
1081     }
1082     return ret;
1083 }
1084 
smooth2(int remove_length,PolygonRef result) const1085 void ConstPolygonRef::smooth2(int remove_length, PolygonRef result) const
1086 {
1087     const ConstPolygonRef& thiss = *this;
1088     ClipperLib::Path* poly = result.path;
1089     if (thiss.size() > 0)
1090     {
1091         poly->push_back(thiss[0]);
1092     }
1093     for (unsigned int poly_idx = 1; poly_idx < thiss.size(); poly_idx++)
1094     {
1095         const Point& last = thiss[poly_idx - 1];
1096         const Point& now = thiss[poly_idx];
1097         const Point& next = thiss[(poly_idx + 1) % thiss.size()];
1098         if (shorterThen(last - now, remove_length) && shorterThen(now - next, remove_length))
1099         {
1100             poly_idx++; // skip the next line piece (dont escalate the removal of edges)
1101             if (poly_idx < thiss.size())
1102             {
1103                 poly->push_back(thiss[poly_idx]);
1104             }
1105         }
1106         else
1107         {
1108             poly->push_back(thiss[poly_idx]);
1109         }
1110     }
1111 }
1112 
smooth2(int remove_length,int min_area) const1113 Polygons Polygons::smooth2(int remove_length, int min_area) const
1114 {
1115     Polygons ret;
1116     for (unsigned int p = 0; p < size(); p++)
1117     {
1118         ConstPolygonRef poly(paths[p]);
1119         if (poly.size() == 0)
1120         {
1121             continue;
1122         }
1123         if (poly.area() < min_area || poly.size() <= 5) // when optimally removing, a poly with 5 pieces results in a triangle. Smaller polys dont have area!
1124         {
1125             ret.add(poly);
1126             continue;
1127         }
1128         if (poly.size() < 4)
1129         {
1130             ret.add(poly);
1131         }
1132         else
1133         {
1134             poly.smooth2(remove_length, ret.newPoly());
1135         }
1136     }
1137     return ret;
1138 }
1139 
area() const1140 double Polygons::area() const
1141 {
1142     double area = 0.0;
1143     for (unsigned int poly_idx = 0; poly_idx < size(); poly_idx++)
1144     {
1145         area += operator[](poly_idx).area();
1146         // note: holes already have negative area
1147     }
1148     return area;
1149 }
1150 
splitIntoParts(bool unionAll) const1151 std::vector<PolygonsPart> Polygons::splitIntoParts(bool unionAll) const
1152 {
1153     std::vector<PolygonsPart> ret;
1154     ClipperLib::Clipper clipper(clipper_init);
1155     ClipperLib::PolyTree resultPolyTree;
1156     clipper.AddPaths(paths, ClipperLib::ptSubject, true);
1157     if (unionAll)
1158         clipper.Execute(ClipperLib::ctUnion, resultPolyTree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
1159     else
1160         clipper.Execute(ClipperLib::ctUnion, resultPolyTree);
1161 
1162     splitIntoParts_processPolyTreeNode(&resultPolyTree, ret);
1163     return ret;
1164 }
1165 
splitIntoParts_processPolyTreeNode(ClipperLib::PolyNode * node,std::vector<PolygonsPart> & ret) const1166 void Polygons::splitIntoParts_processPolyTreeNode(ClipperLib::PolyNode* node, std::vector<PolygonsPart>& ret) const
1167 {
1168     for(int n=0; n<node->ChildCount(); n++)
1169     {
1170         ClipperLib::PolyNode* child = node->Childs[n];
1171         PolygonsPart part;
1172         part.add(child->Contour);
1173         for(int i=0; i<child->ChildCount(); i++)
1174         {
1175             part.add(child->Childs[i]->Contour);
1176             splitIntoParts_processPolyTreeNode(child->Childs[i], ret);
1177         }
1178         ret.push_back(part);
1179     }
1180 }
1181 
tubeShape(const coord_t inner_offset,const coord_t outer_offset) const1182 Polygons Polygons::tubeShape(const coord_t inner_offset, const coord_t outer_offset) const
1183 {
1184     return this->offset(outer_offset).difference(this->offset(-inner_offset));
1185 }
1186 
getPartContaining(unsigned int poly_idx,unsigned int * boundary_poly_idx) const1187 unsigned int PartsView::getPartContaining(unsigned int poly_idx, unsigned int* boundary_poly_idx) const
1188 {
1189     const PartsView& partsView = *this;
1190     for (unsigned int part_idx_now = 0; part_idx_now < partsView.size(); part_idx_now++)
1191     {
1192         const std::vector<unsigned int>& partView = partsView[part_idx_now];
1193         if (partView.size() == 0) { continue; }
1194         std::vector<unsigned int>::const_iterator result = std::find(partView.begin(), partView.end(), poly_idx);
1195         if (result != partView.end())
1196         {
1197             if (boundary_poly_idx) { *boundary_poly_idx = partView[0]; }
1198             return part_idx_now;
1199         }
1200     }
1201     return NO_INDEX;
1202 }
1203 
assemblePart(unsigned int part_idx) const1204 PolygonsPart PartsView::assemblePart(unsigned int part_idx) const
1205 {
1206     const PartsView& partsView = *this;
1207     PolygonsPart ret;
1208     if (part_idx != NO_INDEX)
1209     {
1210         for (unsigned int poly_idx_ff : partsView[part_idx])
1211         {
1212             ret.add(polygons[poly_idx_ff]);
1213         }
1214     }
1215     return ret;
1216 }
1217 
assemblePartContaining(unsigned int poly_idx,unsigned int * boundary_poly_idx) const1218 PolygonsPart PartsView::assemblePartContaining(unsigned int poly_idx, unsigned int* boundary_poly_idx) const
1219 {
1220     PolygonsPart ret;
1221     unsigned int part_idx = getPartContaining(poly_idx, boundary_poly_idx);
1222     if (part_idx != NO_INDEX)
1223     {
1224         return assemblePart(part_idx);
1225     }
1226     return ret;
1227 }
1228 
splitIntoPartsView(bool unionAll)1229 PartsView Polygons::splitIntoPartsView(bool unionAll)
1230 {
1231     Polygons reordered;
1232     PartsView partsView(*this);
1233     ClipperLib::Clipper clipper(clipper_init);
1234     ClipperLib::PolyTree resultPolyTree;
1235     clipper.AddPaths(paths, ClipperLib::ptSubject, true);
1236     if (unionAll)
1237         clipper.Execute(ClipperLib::ctUnion, resultPolyTree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
1238     else
1239         clipper.Execute(ClipperLib::ctUnion, resultPolyTree);
1240 
1241     splitIntoPartsView_processPolyTreeNode(partsView, reordered, &resultPolyTree);
1242 
1243     (*this) = reordered;
1244     return partsView;
1245 }
1246 
splitIntoPartsView_processPolyTreeNode(PartsView & partsView,Polygons & reordered,ClipperLib::PolyNode * node) const1247 void Polygons::splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Polygons& reordered, ClipperLib::PolyNode* node) const
1248 {
1249     for(int n=0; n<node->ChildCount(); n++)
1250     {
1251         ClipperLib::PolyNode* child = node->Childs[n];
1252         partsView.emplace_back();
1253         unsigned int pos = partsView.size() - 1;
1254         partsView[pos].push_back(reordered.size());
1255         reordered.add(child->Contour); //TODO: should this steal the internal representation for speed?
1256         for(int i = 0; i < child->ChildCount(); i++)
1257         {
1258             partsView[pos].push_back(reordered.size());
1259             reordered.add(child->Childs[i]->Contour);
1260             splitIntoPartsView_processPolyTreeNode(partsView, reordered, child->Childs[i]);
1261         }
1262     }
1263 }
1264 
1265 
1266 
1267 }//namespace cura
1268