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