1 //Copyright (c) 2018 Ultimaker B.V.
2 //CuraEngine is released under the terms of the AGPLv3 or higher.
3
4 #include <list>
5 #include <sstream>
6 #include <unordered_set>
7
8 #include "linearAlg2D.h"
9 #include "polygonUtils.h"
10 #include "SparsePointGridInclusive.h"
11 #include "../utils/logoutput.h"
12
13 #ifdef DEBUG
14 #include "AABB.h"
15 #include "SVG.h"
16 #endif
17
18 namespace cura
19 {
20
__anonebed223a0102(Point)21 const std::function<int(Point)> PolygonUtils::no_penalty_function = [](Point){ return 0; };
22
segmentLength(PolygonsPointIndex start,PolygonsPointIndex end)23 int64_t PolygonUtils::segmentLength(PolygonsPointIndex start, PolygonsPointIndex end)
24 {
25 assert(start.poly_idx == end.poly_idx);
26 int64_t segment_length = 0;
27 Point prev_vert = start.p();
28 ConstPolygonRef poly = (*start.polygons)[start.poly_idx];
29 for (unsigned int point_idx = 1; point_idx <= poly.size(); point_idx++)
30 {
31 unsigned int vert_idx = (start.point_idx + point_idx) % poly.size();
32 Point vert = poly[vert_idx];
33 segment_length += vSize(vert - prev_vert);
34
35 if (vert_idx == end.point_idx)
36 { // break at the end of the loop, so that [end] and [start] may be the same
37 return segment_length;
38 }
39 prev_vert = vert;
40 }
41 assert(false && "The segment end should have been encountered!");
42 return segment_length;
43 }
44
spreadDots(PolygonsPointIndex start,PolygonsPointIndex end,unsigned int n_dots,std::vector<ClosestPolygonPoint> & result)45 void PolygonUtils::spreadDots(PolygonsPointIndex start, PolygonsPointIndex end, unsigned int n_dots, std::vector<ClosestPolygonPoint>& result)
46 {
47 assert(start.poly_idx == end.poly_idx);
48 int64_t segment_length = segmentLength(start, end);
49
50 ConstPolygonRef poly = (*start.polygons)[start.poly_idx];
51 unsigned int n_dots_in_between = n_dots;
52 if (start == end)
53 {
54 result.emplace_back(start.p(), start.point_idx, poly);
55 n_dots_in_between--; // generate one less below, because we already pushed a point to the result
56 }
57
58 int64_t wipe_point_dist = segment_length / (n_dots_in_between + 1); // distance between two wipe points; keep a distance at both sides of the segment
59
60 int64_t dist_past_vert_to_insert_point = wipe_point_dist;
61 unsigned int n_points_generated = 0;
62 PolygonsPointIndex vert = start;
63 while (true)
64 {
65 Point p0 = vert.p();
66 Point p1 = vert.next().p();
67 Point p0p1 = p1 - p0;
68 int64_t p0p1_length = vSize(p0p1);
69
70 for ( ; dist_past_vert_to_insert_point < p0p1_length && n_points_generated < n_dots_in_between; dist_past_vert_to_insert_point += wipe_point_dist)
71 {
72 result.emplace_back(p0 + normal(p0p1, dist_past_vert_to_insert_point), vert.point_idx, poly);
73 n_points_generated++;
74 }
75 dist_past_vert_to_insert_point -= p0p1_length;
76
77 ++vert;
78 if (vert == end)
79 { // break at end of loop to allow for [start] and [end] being the same, meaning the full polygon
80 break;
81 }
82 }
83 assert(result.size() == n_dots && "we didn't generate as many wipe locations as we asked for.");
84 }
85
getVertexInwardNormal(ConstPolygonRef poly,unsigned int point_idx)86 Point PolygonUtils::getVertexInwardNormal(ConstPolygonRef poly, unsigned int point_idx)
87 {
88 Point p1 = poly[point_idx];
89
90 int p0_idx;
91 for (p0_idx = int(point_idx) - 1; (unsigned int)p0_idx != point_idx; p0_idx = p0_idx - 1)
92 { // find the last point different from p1
93 if (p0_idx == -1)
94 {
95 p0_idx = poly.size() - 1;
96 }
97 if (poly[p0_idx] != p1)
98 {
99 break;
100 }
101 }
102 Point p0 = poly[p0_idx];
103
104 unsigned int p2_idx;
105 for (p2_idx = point_idx + 1; p2_idx != point_idx; p2_idx = p2_idx + 1)
106 { // find the next point different from p1
107 if (p2_idx == poly.size())
108 {
109 p2_idx = 0;
110 }
111 if (poly[p2_idx] != p1)
112 {
113 break;
114 }
115 }
116 const Point& p2 = poly[p2_idx];
117
118 Point off0 = turn90CCW(normal(p1 - p0, MM2INT(10.0))); // 10.0 for some precision
119 Point off1 = turn90CCW(normal(p2 - p1, MM2INT(10.0))); // 10.0 for some precision
120 Point n = off0 + off1;
121 return n;
122 }
123
124
getBoundaryPointWithOffset(ConstPolygonRef poly,unsigned int point_idx,int64_t offset)125 Point PolygonUtils::getBoundaryPointWithOffset(ConstPolygonRef poly, unsigned int point_idx, int64_t offset)
126 {
127 return poly[point_idx] + normal(getVertexInwardNormal(poly, point_idx), -offset);
128 }
129
moveInsideDiagonally(ClosestPolygonPoint point_on_boundary,int64_t inset)130 Point PolygonUtils::moveInsideDiagonally(ClosestPolygonPoint point_on_boundary, int64_t inset)
131 {
132 if (!point_on_boundary.isValid())
133 {
134 return no_point;
135 }
136 ConstPolygonRef poly = **point_on_boundary.poly;
137 Point p0 = poly[point_on_boundary.point_idx];
138 Point p1 = poly[(point_on_boundary.point_idx + 1) % poly.size()];
139 if (vSize2(p0 - point_on_boundary.location) < vSize2(p1 - point_on_boundary.location))
140 {
141 return point_on_boundary.location + normal(getVertexInwardNormal(poly, point_on_boundary.point_idx), inset);
142 }
143 else
144 {
145 return point_on_boundary.location + normal(getVertexInwardNormal(poly, (point_on_boundary.point_idx + 1) % poly.size()), inset);
146 }
147 }
148
149
moveOutside(const Polygons & polygons,Point & from,int distance,int64_t maxDist2)150 unsigned int PolygonUtils::moveOutside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
151 {
152 return moveInside(polygons, from, -distance, maxDist2);
153 }
154
moveInside2(const Polygons & polygons,Point & from,const int distance,const int64_t max_dist2,const Polygons * loc_to_line_polygons,const LocToLineGrid * loc_to_line_grid,const std::function<int (Point)> & penalty_function)155 ClosestPolygonPoint PolygonUtils::moveInside2(const Polygons& polygons, Point& from, const int distance, const int64_t max_dist2, const Polygons* loc_to_line_polygons, const LocToLineGrid* loc_to_line_grid, const std::function<int(Point)>& penalty_function)
156 {
157 std::optional<ClosestPolygonPoint> closest_polygon_point;
158 if (loc_to_line_grid)
159 {
160 closest_polygon_point = findClose(from, *loc_to_line_polygons, *loc_to_line_grid, penalty_function);
161 }
162 if (!closest_polygon_point)
163 {
164 closest_polygon_point = findClosest(from, polygons, penalty_function);
165 }
166 return _moveInside2(*closest_polygon_point, distance, from, max_dist2);
167 }
168
moveInside2(const Polygons & loc_to_line_polygons,ConstPolygonRef polygon,Point & from,const int distance,const int64_t max_dist2,const LocToLineGrid * loc_to_line_grid,const std::function<int (Point)> & penalty_function)169 ClosestPolygonPoint PolygonUtils::moveInside2(const Polygons& loc_to_line_polygons, ConstPolygonRef polygon, Point& from, const int distance, const int64_t max_dist2, const LocToLineGrid* loc_to_line_grid, const std::function<int(Point)>& penalty_function)
170 {
171 std::optional<ClosestPolygonPoint> closest_polygon_point;
172 if (loc_to_line_grid)
173 {
174 closest_polygon_point = findClose(from, loc_to_line_polygons, *loc_to_line_grid, penalty_function);
175 }
176 if (!closest_polygon_point)
177 {
178 closest_polygon_point = findClosest(from, polygon, penalty_function);
179 }
180 return _moveInside2(*closest_polygon_point, distance, from, max_dist2);
181 }
182
_moveInside2(const ClosestPolygonPoint & closest_polygon_point,const int distance,Point & from,const int64_t max_dist2)183 ClosestPolygonPoint PolygonUtils::_moveInside2(const ClosestPolygonPoint& closest_polygon_point, const int distance, Point& from, const int64_t max_dist2)
184 {
185 if (!closest_polygon_point.isValid())
186 {
187 return ClosestPolygonPoint(); // stub with invalid indices to signify we haven't found any
188 }
189 const Point v_boundary_from = from - closest_polygon_point.location;
190 Point result = moveInside(closest_polygon_point, distance);
191 const Point v_boundary_result = result - closest_polygon_point.location;
192 if (dot(v_boundary_result, v_boundary_from) > 0)
193 { // point was already on the correct side of the polygon
194 if (vSize2(v_boundary_from) > distance * distance)
195 { // [from] was already on the correct side of the boudary by enough distance
196 // don't change [from]
197 return closest_polygon_point;
198 }
199 else
200 {
201 from = result;
202 return closest_polygon_point;
203 }
204 }
205 else
206 {
207 if (vSize2(v_boundary_from) > max_dist2)
208 {
209 return ClosestPolygonPoint(*closest_polygon_point.poly); // stub with invalid indices to signify we haven't found any
210 }
211 else
212 {
213 from = result;
214 return closest_polygon_point;
215 }
216 }
217 }
218
219
220 /*
221 * Implementation assumes moving inside, but moving outside should just as well be possible.
222 */
moveInside(const Polygons & polygons,Point & from,int distance,int64_t maxDist2)223 unsigned int PolygonUtils::moveInside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
224 {
225 Point ret = from;
226 int64_t bestDist2 = std::numeric_limits<int64_t>::max();
227 unsigned int bestPoly = NO_INDEX;
228 bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
229 for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
230 {
231 ConstPolygonRef poly = polygons[poly_idx];
232 if (poly.size() < 2)
233 continue;
234 Point p0 = poly[poly.size()-2];
235 Point p1 = poly.back();
236 // because we compare with vSize2 here (no division by zero), we also need to compare by vSize2 inside the loop
237 // to avoid integer rounding edge cases
238 bool projected_p_beyond_prev_segment = dot(p1 - p0, from - p0) >= vSize2(p1 - p0);
239 for(const Point& p2 : poly)
240 {
241 // X = A + Normal(B-A) * (((B-A) dot (P-A)) / VSize(B-A));
242 // = A + (B-A) * ((B-A) dot (P-A)) / VSize2(B-A);
243 // X = P projected on AB
244 const Point& a = p1;
245 const Point& b = p2;
246 const Point& p = from;
247 Point ab = b - a;
248 Point ap = p - a;
249 int64_t ab_length2 = vSize2(ab);
250 if(ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other.
251 {
252 p1 = p2; //Skip only one of the points.
253 continue;
254 }
255 int64_t dot_prod = dot(ab, ap);
256 if (dot_prod <= 0) // x is projected to before ab
257 {
258 if (projected_p_beyond_prev_segment)
259 { // case which looks like: > .
260 projected_p_beyond_prev_segment = false;
261 Point& x = p1;
262
263 int64_t dist2 = vSize2(x - p);
264 if (dist2 < bestDist2)
265 {
266 bestDist2 = dist2;
267 bestPoly = poly_idx;
268 if (distance == 0) { ret = x; }
269 else
270 {
271 Point inward_dir = turn90CCW(normal(ab, MM2INT(10.0)) + normal(p1 - p0, MM2INT(10.0))); // inward direction irrespective of sign of [distance]
272 // MM2INT(10.0) to retain precision for the eventual normalization
273 ret = x + normal(inward_dir, distance);
274 is_already_on_correct_side_of_boundary = dot(inward_dir, p - x) * distance >= 0;
275 }
276 }
277 }
278 else
279 {
280 projected_p_beyond_prev_segment = false;
281 p0 = p1;
282 p1 = p2;
283 continue;
284 }
285 }
286 else if (dot_prod >= ab_length2) // x is projected to beyond ab
287 {
288 projected_p_beyond_prev_segment = true;
289 p0 = p1;
290 p1 = p2;
291 continue;
292 }
293 else
294 { // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
295 projected_p_beyond_prev_segment = false;
296 Point x = a + ab * dot_prod / ab_length2;
297
298 int64_t dist2 = vSize2(p - x);
299 if (dist2 < bestDist2)
300 {
301 bestDist2 = dist2;
302 bestPoly = poly_idx;
303 if (distance == 0) { ret = x; }
304 else
305 {
306 Point inward_dir = turn90CCW(normal(ab, distance)); // inward or outward depending on the sign of [distance]
307 ret = x + inward_dir;
308 is_already_on_correct_side_of_boundary = dot(inward_dir, p - x) >= 0;
309 }
310 }
311 }
312
313
314 p0 = p1;
315 p1 = p2;
316 }
317 }
318 if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
319 {
320 if (bestDist2 < distance * distance)
321 {
322 from = ret;
323 }
324 else
325 {
326 // from = from; // original point stays unaltered. It is already inside by enough distance
327 }
328 return bestPoly;
329 }
330 else if (bestDist2 < maxDist2)
331 {
332 from = ret;
333 return bestPoly;
334 }
335 return NO_INDEX;
336 }
337
338 //Version that works on single PolygonRef.
moveInside(const ConstPolygonRef polygon,Point & from,int distance,int64_t maxDist2)339 unsigned int PolygonUtils::moveInside(const ConstPolygonRef polygon, Point& from, int distance, int64_t maxDist2)
340 {
341 //TODO: This is copied from the moveInside of Polygons.
342 /*
343 We'd like to use this function as subroutine in moveInside(Polygons...), but
344 then we'd need to recompute the distance of the point to the polygon, which
345 is expensive. Or we need to return the distance. We need the distance there
346 to compare with the distance to other polygons.
347 */
348 Point ret = from;
349 int64_t bestDist2 = std::numeric_limits<int64_t>::max();
350 bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
351
352 if (polygon.size() < 2)
353 {
354 return 0;
355 }
356 Point p0 = polygon[polygon.size() - 2];
357 Point p1 = polygon.back();
358 // because we compare with vSize2 here (no division by zero), we also need to compare by vSize2 inside the loop
359 // to avoid integer rounding edge cases
360 bool projected_p_beyond_prev_segment = dot(p1 - p0, from - p0) >= vSize2(p1 - p0);
361 for(const Point& p2 : polygon)
362 {
363 // X = A + Normal(B-A) * (((B-A) dot (P-A)) / VSize(B-A));
364 // = A + (B-A) * ((B-A) dot (P-A)) / VSize2(B-A);
365 // X = P projected on AB
366 const Point& a = p1;
367 const Point& b = p2;
368 const Point& p = from;
369 Point ab = b - a;
370 Point ap = p - a;
371 int64_t ab_length2 = vSize2(ab);
372 if(ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other.
373 {
374 p1 = p2; //Skip only one of the points.
375 continue;
376 }
377 int64_t dot_prod = dot(ab, ap);
378 if (dot_prod <= 0) // x is projected to before ab
379 {
380 if (projected_p_beyond_prev_segment)
381 { // case which looks like: > .
382 projected_p_beyond_prev_segment = false;
383 Point& x = p1;
384
385 int64_t dist2 = vSize2(x - p);
386 if (dist2 < bestDist2)
387 {
388 bestDist2 = dist2;
389 if (distance == 0)
390 {
391 ret = x;
392 }
393 else
394 {
395 Point inward_dir = turn90CCW(normal(ab, MM2INT(10.0)) + normal(p1 - p0, MM2INT(10.0))); // inward direction irrespective of sign of [distance]
396 // MM2INT(10.0) to retain precision for the eventual normalization
397 ret = x + normal(inward_dir, distance);
398 is_already_on_correct_side_of_boundary = dot(inward_dir, p - x) * distance >= 0;
399 }
400 }
401 }
402 else
403 {
404 projected_p_beyond_prev_segment = false;
405 p0 = p1;
406 p1 = p2;
407 continue;
408 }
409 }
410 else if (dot_prod >= ab_length2) // x is projected to beyond ab
411 {
412 projected_p_beyond_prev_segment = true;
413 p0 = p1;
414 p1 = p2;
415 continue;
416 }
417 else
418 { // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
419 projected_p_beyond_prev_segment = false;
420 Point x = a + ab * dot_prod / ab_length2;
421
422 int64_t dist2 = vSize2(p - x);
423 if (dist2 < bestDist2)
424 {
425 bestDist2 = dist2;
426 if (distance == 0)
427 {
428 ret = x;
429 }
430 else
431 {
432 Point inward_dir = turn90CCW(normal(ab, distance)); // inward or outward depending on the sign of [distance]
433 ret = x + inward_dir;
434 is_already_on_correct_side_of_boundary = dot(inward_dir, p - x) >= 0;
435 }
436 }
437 }
438
439 p0 = p1;
440 p1 = p2;
441 }
442
443 if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
444 {
445 if (bestDist2 < distance * distance)
446 {
447 from = ret;
448 }
449 }
450 else if (bestDist2 < maxDist2)
451 {
452 from = ret;
453 }
454 return 0;
455 }
456
moveOutside(const ClosestPolygonPoint & cpp,const int distance)457 Point PolygonUtils::moveOutside(const ClosestPolygonPoint& cpp, const int distance)
458 {
459 return moveInside(cpp, -distance);
460 }
461
moveInside(const ClosestPolygonPoint & cpp,const int distance)462 Point PolygonUtils::moveInside(const ClosestPolygonPoint& cpp, const int distance)
463 {
464 if (!cpp.isValid())
465 {
466 return no_point;
467 }
468 if (distance == 0)
469 { // the point which is assumed to be on the boundary doesn't have to be moved
470 return cpp.location;
471 }
472 ConstPolygonRef poly = *cpp.poly;
473 unsigned int point_idx = cpp.point_idx;
474 const Point& on_boundary = cpp.location;
475
476 const Point& p1 = poly[point_idx];
477 unsigned int p2_idx;
478 for (p2_idx = point_idx + 1; p2_idx != point_idx; p2_idx = p2_idx + 1)
479 { // find the next point different from p1
480 if (p2_idx == poly.size())
481 {
482 p2_idx = 0;
483 }
484 if (poly[p2_idx] != p1)
485 {
486 break;
487 }
488 }
489 const Point& p2 = poly[p2_idx];
490
491 if (on_boundary == p1)
492 {
493 return getBoundaryPointWithOffset(poly, point_idx, -distance);
494 }
495 else if (on_boundary == p2)
496 {
497 return getBoundaryPointWithOffset(poly, p2_idx, -distance);
498 }
499 else
500 {
501 const Point& x = on_boundary; // on_boundary is already projected on p1-p2
502
503 Point inward_dir = turn90CCW(normal(p2 - p1, distance));
504 return x + inward_dir;
505 }
506 }
507
ensureInsideOrOutside(const Polygons & polygons,Point & from,int preferred_dist_inside,int64_t max_dist2,const Polygons * loc_to_line_polygons,const LocToLineGrid * loc_to_line_grid,const std::function<int (Point)> & penalty_function)508 ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons, Point& from, int preferred_dist_inside, int64_t max_dist2, const Polygons* loc_to_line_polygons, const LocToLineGrid* loc_to_line_grid, const std::function<int(Point)>& penalty_function)
509 {
510 const ClosestPolygonPoint closest_polygon_point = moveInside2(polygons, from, preferred_dist_inside, max_dist2, loc_to_line_polygons, loc_to_line_grid, penalty_function);
511 return ensureInsideOrOutside(polygons, from, closest_polygon_point, preferred_dist_inside, loc_to_line_polygons, loc_to_line_grid, penalty_function);
512 }
513
ensureInsideOrOutside(const Polygons & polygons,Point & from,const ClosestPolygonPoint & closest_polygon_point,int preferred_dist_inside,const Polygons * loc_to_line_polygons,const LocToLineGrid * loc_to_line_grid,const std::function<int (Point)> & penalty_function)514 ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons, Point& from, const ClosestPolygonPoint& closest_polygon_point, int preferred_dist_inside, const Polygons* loc_to_line_polygons, const LocToLineGrid* loc_to_line_grid, const std::function<int(Point)>& penalty_function)
515 {
516 if (!closest_polygon_point.isValid())
517 {
518 return ClosestPolygonPoint(); // we couldn't move inside
519 }
520 ConstPolygonRef closest_poly = *closest_polygon_point.poly;
521 bool is_outside_boundary = closest_poly.orientation();
522
523 {
524 bool is_inside = closest_poly.inside(from) == is_outside_boundary; // inside a hole is outside the part
525 if (is_inside == (preferred_dist_inside > 0))
526 { // we ended up on the right side of the polygon
527 // assume we didn't overshoot another polygon in [polygons]
528 return closest_polygon_point;
529 }
530 }
531
532 // try once more with half the preferred distance inside
533 int64_t max_dist2_here = std::numeric_limits<int64_t>::max(); // we already concluded we are close enough to the closest_poly when we obtained the closest_polygon_point
534 moveInside2(*loc_to_line_polygons, closest_poly, from, preferred_dist_inside / 2, max_dist2_here, loc_to_line_grid, penalty_function);
535 bool is_inside = closest_poly.inside(from) == is_outside_boundary; // inside a hole is outside the part
536 if (is_inside == (preferred_dist_inside > 0))
537 { // we ended up on the right side of the polygon
538 // assume we didn't overshoot another polygon in [polygons]
539 return closest_polygon_point;
540 }
541 // if above fails, we perform an offset and sit directly on the offsetted polygon (and keep the result from the above moveInside)
542 // The offset is performed on the closest reference polygon in order to save computation time
543 else
544 {
545 const coord_t offset = (is_outside_boundary) ? -preferred_dist_inside : preferred_dist_inside; // perform inset on outer boundary and outset on holes
546 Polygons insetted = closest_poly.offset(offset / 2); // perform less inset, because chances are (thin parts of) the polygon will disappear, given that moveInside did an overshoot
547 if (insetted.size() == 0)
548 {
549 return ClosestPolygonPoint(); // we couldn't move inside
550 }
551 ClosestPolygonPoint inside = findClosest(from, insetted, penalty_function);
552 if (inside.isValid())
553 {
554 bool is_inside = polygons.inside(inside.location);
555 if (is_inside != (preferred_dist_inside > 0))
556 {
557 // Insetting from the reference polygon ended up outside another polygon.
558 // Perform an offset on all polygons instead.
559 Polygons all_insetted = polygons.offset(-preferred_dist_inside);
560 ClosestPolygonPoint overall_inside = findClosest(from, all_insetted, penalty_function);
561 bool overall_is_inside = polygons.inside(overall_inside.location);
562 if (overall_is_inside != (preferred_dist_inside > 0))
563 {
564 #ifdef DEBUG
565 try
566 {
567 int offset_performed = offset / 2;
568 AABB aabb(polygons);
569 aabb.expand(std::abs(preferred_dist_inside) * 2);
570 SVG svg("debug.html", aabb);
571 svg.writeComment("Original polygon in black");
572 svg.writePolygons(polygons, SVG::Color::BLACK);
573 for (auto poly : polygons)
574 {
575 for (auto point : poly)
576 {
577 svg.writePoint(point, true, 2);
578 }
579 }
580 std::stringstream ss;
581 svg.writeComment("Reference polygon in yellow");
582 svg.writePolygon(closest_poly, SVG::Color::YELLOW);
583 ss << "Offsetted polygon in blue with offset " << offset_performed;
584 svg.writeComment(ss.str());
585 svg.writePolygons(insetted, SVG::Color::BLUE);
586 for (auto poly : insetted)
587 {
588 for (auto point : poly)
589 {
590 svg.writePoint(point, true, 2);
591 }
592 }
593 svg.writeComment("From location");
594 svg.writePoint(from, true, 5, SVG::Color::GREEN);
595 svg.writeComment("Location computed to be inside the black polygon");
596 svg.writePoint(inside.location, true, 5, SVG::Color::RED);
597 }
598 catch(...)
599 {
600 }
601 logError("Clipper::offset failed. See generated debug.html!\n\tBlack is original\n\tBlue is offsetted polygon\n");
602 #endif
603 return ClosestPolygonPoint();
604 }
605 inside = overall_inside;
606 }
607 from = inside.location;
608 } // otherwise we just return the closest polygon point without modifying the from location
609 return closest_polygon_point; // don't return a ClosestPolygonPoint with a reference to the above local polygons variable
610 }
611 }
612
613
614
findConnection(ConstPolygonRef poly1,Polygons & polys2,coord_t min_connection_length,coord_t max_connection_length,std::function<bool (std::pair<ClosestPolygonPoint,ClosestPolygonPoint>)> precondition)615 std::pair<ClosestPolygonPoint, ClosestPolygonPoint> PolygonUtils::findConnection(ConstPolygonRef poly1, Polygons& polys2, coord_t min_connection_length, coord_t max_connection_length, std::function<bool (std::pair<ClosestPolygonPoint, ClosestPolygonPoint>)> precondition)
616 {
617 ClosestPolygonPoint invalid;
618 std::pair<ClosestPolygonPoint, ClosestPolygonPoint> ret = std::make_pair(invalid, invalid);
619 if (poly1.empty() || polys2.empty())
620 {
621 return ret;
622 }
623
624 const coord_t min_connection_dist2 = min_connection_length * min_connection_length;
625 const coord_t max_connection_dist2 = max_connection_length * max_connection_length;
626
627 LocToLineGrid* grid = PolygonUtils::createLocToLineGrid(polys2, max_connection_length);
628
629
630 std::unordered_set<std::pair<size_t, PolygonsPointIndex>> checked_segment_pairs; // pairs of index into segment start on poly1 and PolygonsPointIndex to segment start on polys2
631
632 for (size_t point_idx = 0; point_idx < poly1.size(); point_idx++)
633 {
634 std::function<bool (const PolygonsPointIndex&)> process_elem_func =
635 [&, point_idx](const PolygonsPointIndex& line_from)
636 {
637 std::pair<size_t, PolygonsPointIndex> segment_pair = std::make_pair(point_idx, line_from);
638 if (checked_segment_pairs.count(segment_pair) > 0)
639 { // these two line segments were already checked
640 return true; // continue looking for connections
641 }
642
643 Point a1 = poly1[point_idx];
644 Point a2 = poly1[(point_idx + 1) % poly1.size()];
645 Point b1 = line_from.p();
646 Point b2 = line_from.next().p();
647 std::pair<Point, Point> connection = LinearAlg2D::getClosestConnection(a1, a2, b1, b2);
648 coord_t dist2 = vSize2(connection.first - connection.second);
649 ret = std::make_pair(
650 ClosestPolygonPoint(connection.first, point_idx, poly1),
651 ClosestPolygonPoint(connection.second, line_from.point_idx, polys2[line_from.poly_idx], line_from.poly_idx));
652 if (min_connection_dist2 < dist2 && dist2 < max_connection_dist2
653 && precondition(ret))
654 {
655 return false; // stop the search; break the for-loop
656 }
657
658 checked_segment_pairs.emplace(point_idx, line_from);
659 return true; // continue looking for connections
660 };
661
662 std::pair<Point, Point> line = std::make_pair(poly1[point_idx], poly1[(point_idx + 1) % poly1.size()]);
663 Point normal_vector = normal(turn90CCW(line.second - line.first), max_connection_length);
664 std::pair<Point, Point> line2 = std::make_pair(line.first + normal_vector, line.second + normal_vector); // for neighborhood around the line
665 std::pair<Point, Point> line3 = std::make_pair(line.first - normal_vector, line.second - normal_vector); // for neighborhood around the line
666
667 bool continue_;
668 continue_ = grid->processLine(line, process_elem_func);
669 if (!continue_) break;
670 continue_ = grid->processLine(line2, process_elem_func);
671 if (!continue_) break;
672 continue_ = grid->processLine(line3, process_elem_func);
673 if (!continue_) break;
674 }
675 ret.first.poly_idx = 0;
676 delete grid;
677 return ret;
678 }
679
findSmallestConnection(ClosestPolygonPoint & poly1_result,ClosestPolygonPoint & poly2_result)680 void PolygonUtils::findSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result)
681 {
682 if (!poly1_result.poly || !poly2_result.poly)
683 {
684 return;
685 }
686 ConstPolygonRef poly1 = *poly1_result.poly;
687 ConstPolygonRef poly2 = *poly2_result.poly;
688 if (poly1.size() == 0 || poly2.size() == 0)
689 {
690 return;
691 }
692
693 Point center1 = poly1[0];
694 ClosestPolygonPoint intermediate_poly2_result = findClosest(center1, poly2);
695 ClosestPolygonPoint intermediate_poly1_result = findClosest(intermediate_poly2_result.p(), poly1);
696
697 poly2_result = findClosest(intermediate_poly1_result.p(), poly2);
698 poly1_result = findClosest(poly2_result.p(), poly1);
699 walkToNearestSmallestConnection(poly1_result, poly2_result);
700 }
701
walkToNearestSmallestConnection(ClosestPolygonPoint & poly1_result,ClosestPolygonPoint & poly2_result)702 void PolygonUtils::walkToNearestSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result)
703 {
704 if (!poly1_result.isValid() || !poly2_result.isValid())
705 {
706 return;
707 }
708 ConstPolygonRef poly1 = *poly1_result.poly;
709 ConstPolygonRef poly2 = *poly2_result.poly;
710 size_t poly1_idx = poly1_result.poly_idx;
711 size_t poly2_idx = poly2_result.poly_idx;
712 if (poly1_result.point_idx == NO_INDEX || poly2_result.point_idx == NO_INDEX)
713 {
714 return;
715 }
716
717 int equilibirum_limit = 100; // hard coded value
718 for (int loop_counter = 0; loop_counter < equilibirum_limit; loop_counter++)
719 {
720 unsigned int pos1_before = poly1_result.point_idx;
721 poly1_result = findNearestClosest(poly2_result.location, poly1, poly1_result.point_idx);
722 unsigned int pos2_before = poly2_result.point_idx;
723 poly2_result = findNearestClosest(poly1_result.location, poly2, poly2_result.point_idx);
724
725 if (poly1_result.point_idx == pos1_before && poly2_result.point_idx == pos2_before)
726 {
727 break;
728 }
729 }
730
731 // check surrounding verts in order to prevent local optima like the following:
732 //o o
733 // \.....|
734 // \_.-'|
735 // \---|
736 // \-'|
737 // o o >> should find connection here
738 coord_t best_distance2 = vSize2(poly1_result.p() - poly2_result.p());
739 auto check_neighboring_vert = [&best_distance2](ConstPolygonRef from_poly, ConstPolygonRef to_poly, ClosestPolygonPoint& from_poly_result, ClosestPolygonPoint& to_poly_result, bool vertex_after)
740 {
741 const Point after_poly2_result = to_poly[(to_poly_result.point_idx + vertex_after) % to_poly.size()];
742 const ClosestPolygonPoint poly1_after_poly2_result = findNearestClosest(after_poly2_result, from_poly, from_poly_result.point_idx);
743 const coord_t poly1_after_poly2_result_dist2 = vSize2(poly1_after_poly2_result.p() - after_poly2_result);
744 if (poly1_after_poly2_result_dist2 < best_distance2)
745 {
746 from_poly_result = poly1_after_poly2_result;
747 to_poly_result.location = after_poly2_result;
748 best_distance2 = poly1_after_poly2_result_dist2;
749 }
750 };
751 check_neighboring_vert(poly1, poly2, poly1_result, poly2_result, false);
752 check_neighboring_vert(poly1, poly2, poly1_result, poly2_result, true);
753 check_neighboring_vert(poly2, poly1, poly2_result, poly1_result, false);
754 check_neighboring_vert(poly2, poly1, poly2_result, poly1_result, true);
755
756 poly1_result.poly_idx = poly1_idx;
757 poly2_result.poly_idx = poly2_idx;
758 }
759
findNearestClosest(Point from,ConstPolygonRef polygon,int start_idx)760 ClosestPolygonPoint PolygonUtils::findNearestClosest(Point from, ConstPolygonRef polygon, int start_idx)
761 {
762 ClosestPolygonPoint forth = findNearestClosest(from, polygon, start_idx, 1);
763 if (!forth.isValid())
764 {
765 return forth; // stop computation
766 }
767 ClosestPolygonPoint back = findNearestClosest(from, polygon, start_idx, -1);
768 assert(back.isValid());
769 if (vSize2(forth.location - from) < vSize2(back.location - from))
770 {
771 return forth;
772 }
773 else
774 {
775 return back;
776 }
777 }
778
findNearestClosest(Point from,ConstPolygonRef polygon,int start_idx,int direction)779 ClosestPolygonPoint PolygonUtils::findNearestClosest(Point from, ConstPolygonRef polygon, int start_idx, int direction)
780 {
781 if (polygon.size() == 0)
782 {
783 return ClosestPolygonPoint(polygon);
784 }
785 Point aPoint = polygon[0];
786 Point best = aPoint;
787
788 int64_t closestDist = vSize2(from - best);
789 int bestPos = 0;
790
791 size_t poly_size = polygon.size();
792 for (size_t p = 0; p < poly_size; p++)
793 {
794 int p1_idx = (poly_size + direction * p + start_idx) % poly_size;
795 int p2_idx = (poly_size + direction * (p + 1) + start_idx) % poly_size;
796 const Point& p1 = polygon[p1_idx];
797 const Point& p2 = polygon[p2_idx];
798
799 Point closest_here = LinearAlg2D::getClosestOnLineSegment(from, p1 ,p2);
800 int64_t dist = vSize2(from - closest_here);
801 if (dist < closestDist)
802 {
803 best = closest_here;
804 closestDist = dist;
805 bestPos = (direction > 0) ? p1_idx : p2_idx;
806 }
807 else
808 {
809 return ClosestPolygonPoint(best, bestPos, polygon);
810 }
811 }
812
813 return ClosestPolygonPoint(best, bestPos, polygon);
814 }
815
findClosest(Point from,const Polygons & polygons,const std::function<int (Point)> & penalty_function)816 ClosestPolygonPoint PolygonUtils::findClosest(Point from, const Polygons& polygons, const std::function<int(Point)>& penalty_function)
817 {
818 ClosestPolygonPoint none;
819
820 if (polygons.size() == 0)
821 {
822 return none;
823 }
824 ConstPolygonPointer any_polygon = polygons[0];
825 unsigned int any_poly_idx;
826 for (any_poly_idx = 0; any_poly_idx < polygons.size(); any_poly_idx++)
827 { // find first point in all polygons
828 if (polygons[any_poly_idx].size() > 0)
829 {
830 any_polygon = polygons[any_poly_idx];
831 break;
832 }
833 }
834 if (any_polygon->size() == 0)
835 {
836 return none;
837 }
838 ClosestPolygonPoint best((*any_polygon)[0], 0, *any_polygon, any_poly_idx);
839
840 int64_t closestDist2_score = vSize2(from - best.location) + penalty_function(best.location);
841
842 for (unsigned int ply = 0; ply < polygons.size(); ply++)
843 {
844 ConstPolygonRef poly = polygons[ply];
845 if (poly.size() == 0) continue;
846 ClosestPolygonPoint closest_here = findClosest(from, poly, penalty_function);
847 if (!closest_here.isValid())
848 {
849 continue;
850 }
851 int64_t dist2_score = vSize2(from - closest_here.location) + penalty_function(closest_here.location);
852 if (dist2_score < closestDist2_score)
853 {
854 best = closest_here;
855 closestDist2_score = dist2_score;
856 best.poly_idx = ply;
857 }
858 }
859
860 return best;
861 }
862
findClosest(Point from,ConstPolygonRef polygon,const std::function<int (Point)> & penalty_function)863 ClosestPolygonPoint PolygonUtils::findClosest(Point from, ConstPolygonRef polygon, const std::function<int(Point)>& penalty_function)
864 {
865 if (polygon.size() == 0)
866 {
867 return ClosestPolygonPoint(polygon);
868 }
869 Point aPoint = polygon[0];
870 Point best = aPoint;
871
872 int64_t closestDist2_score = vSize2(from - best) + penalty_function(best);
873 int bestPos = 0;
874
875 for (unsigned int p = 0; p<polygon.size(); p++)
876 {
877 const Point& p1 = polygon[p];
878
879 unsigned int p2_idx = p+1;
880 if (p2_idx >= polygon.size()) p2_idx = 0;
881 const Point& p2 = polygon[p2_idx];
882
883 Point closest_here = LinearAlg2D::getClosestOnLineSegment(from, p1 ,p2);
884 int64_t dist2_score = vSize2(from - closest_here) + penalty_function(closest_here);
885 if (dist2_score < closestDist2_score)
886 {
887 best = closest_here;
888 closestDist2_score = dist2_score;
889 bestPos = p;
890 }
891 }
892
893 return ClosestPolygonPoint(best, bestPos, polygon);
894 }
895
findNearestVert(const Point from,const Polygons & polys)896 PolygonsPointIndex PolygonUtils::findNearestVert(const Point from, const Polygons& polys)
897 {
898 int64_t best_dist2 = std::numeric_limits<int64_t>::max();
899 PolygonsPointIndex closest_vert;
900 for (unsigned int poly_idx = 0; poly_idx < polys.size(); poly_idx++)
901 {
902 ConstPolygonRef poly = polys[poly_idx];
903 for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
904 {
905 int64_t dist2 = vSize2(poly[point_idx] - from);
906 if (dist2 < best_dist2)
907 {
908 best_dist2 = dist2;
909 closest_vert = PolygonsPointIndex(&polys, poly_idx, point_idx);
910 }
911 }
912 }
913 return closest_vert;
914 }
915
findNearestVert(const Point from,ConstPolygonRef poly)916 unsigned int PolygonUtils::findNearestVert(const Point from, ConstPolygonRef poly)
917 {
918 int64_t best_dist2 = std::numeric_limits<int64_t>::max();
919 unsigned int closest_vert_idx = -1;
920 for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
921 {
922 int64_t dist2 = vSize2(poly[point_idx] - from);
923 if (dist2 < best_dist2)
924 {
925 best_dist2 = dist2;
926 closest_vert_idx = point_idx;
927 }
928 }
929 return closest_vert_idx;
930 }
931
932
createLocToLineGrid(const Polygons & polygons,int square_size)933 LocToLineGrid* PolygonUtils::createLocToLineGrid(const Polygons& polygons, int square_size)
934 {
935 unsigned int n_points = 0;
936 for (const auto& poly : polygons)
937 {
938 n_points += poly.size();
939 }
940
941 LocToLineGrid* ret = new LocToLineGrid(square_size, n_points);
942
943 for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
944 {
945 ConstPolygonRef poly = polygons[poly_idx];
946 for (unsigned int point_idx = 0; point_idx < poly.size(); point_idx++)
947 {
948 ret->insert(PolygonsPointIndex(&polygons, poly_idx, point_idx));
949 }
950
951 }
952 return ret;
953 }
954
955 /*
956 * The current implemetnation can check the same line segment multiple times,
957 * since the same line segment can occur in multiple cells if it it longer than the cell size of the SparsePointGridInclusive.
958 *
959 * We could skip the duplication by keeping a vector of vectors of bools.
960 *
961 */
findClose(Point from,const Polygons & polygons,const LocToLineGrid & loc_to_line,const std::function<int (Point)> & penalty_function)962 std::optional<ClosestPolygonPoint> PolygonUtils::findClose(
963 Point from, const Polygons& polygons,
964 const LocToLineGrid& loc_to_line,
965 const std::function<int(Point)>& penalty_function)
966 {
967 std::vector<PolygonsPointIndex> near_lines =
968 loc_to_line.getNearby(from, loc_to_line.getCellSize());
969
970 Point best(0, 0);
971
972 int64_t closest_dist2_score = std::numeric_limits<int64_t>::max();
973 PolygonsPointIndex best_point_poly_idx(nullptr, NO_INDEX, NO_INDEX);
974 for (PolygonsPointIndex& point_poly_index : near_lines)
975 {
976 ConstPolygonRef poly = polygons[point_poly_index.poly_idx];
977 const Point& p1 = poly[point_poly_index.point_idx];
978 const Point& p2 = poly[(point_poly_index.point_idx + 1) % poly.size()];
979
980 Point closest_here = LinearAlg2D::getClosestOnLineSegment(from, p1 ,p2);
981 int64_t dist2_score = vSize2(from - closest_here) + penalty_function(closest_here);
982 if (dist2_score < closest_dist2_score)
983 {
984 best = closest_here;
985 closest_dist2_score = dist2_score;
986 best_point_poly_idx = point_poly_index;
987 }
988 }
989 if (best_point_poly_idx.poly_idx == NO_INDEX)
990 {
991 return std::optional<ClosestPolygonPoint>();
992 }
993 else
994 {
995 bool bs_arg = true; // doesn't mean anything. Just to make clear we call the variable arguments of the constructor.
996 return std::optional<ClosestPolygonPoint>(bs_arg, best, best_point_poly_idx.point_idx, polygons[best_point_poly_idx.poly_idx], best_point_poly_idx.poly_idx);
997 }
998 }
999
1000
findClose(ConstPolygonRef from,const Polygons & destination,const LocToLineGrid & destination_loc_to_line,const std::function<int (Point)> & penalty_function)1001 std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> PolygonUtils::findClose(
1002 ConstPolygonRef from, const Polygons& destination,
1003 const LocToLineGrid& destination_loc_to_line,
1004 const std::function<int(Point)>& penalty_function)
1005 {
1006 std::vector<std::pair<ClosestPolygonPoint, ClosestPolygonPoint>> ret;
1007 int p0_idx = from.size() - 1;
1008 Point p0(from[p0_idx]);
1009 int grid_size = destination_loc_to_line.getCellSize();
1010 for (unsigned int p1_idx = 0; p1_idx < from.size(); p1_idx++)
1011 {
1012 const Point& p1 = from[p1_idx];
1013 std::optional<ClosestPolygonPoint> best_here = findClose(p1, destination, destination_loc_to_line, penalty_function);
1014 if (best_here)
1015 {
1016 ret.push_back(std::make_pair(ClosestPolygonPoint(p1, p1_idx, from), *best_here));
1017 }
1018 Point p0p1 = p1 - p0;
1019 int dist_to_p1 = vSize(p0p1);
1020 for (unsigned int middle_point_nr = 1; dist_to_p1 > grid_size * 2; ++middle_point_nr)
1021 {
1022 Point x = p0 + normal(p0p1, middle_point_nr * grid_size);
1023 dist_to_p1 -= grid_size;
1024
1025 std::optional<ClosestPolygonPoint> best_here = findClose(x, destination, destination_loc_to_line, penalty_function);
1026 if (best_here)
1027 {
1028 ret.push_back(std::make_pair(ClosestPolygonPoint(x, p0_idx, from), *best_here));
1029 }
1030 }
1031 p0 = p1;
1032 p0_idx = p1_idx;
1033 }
1034 return ret;
1035 }
1036
1037
1038
1039
1040
getNextPointWithDistance(Point from,int64_t dist,ConstPolygonRef poly,int start_idx,int poly_start_idx,GivenDistPoint & result)1041 bool PolygonUtils::getNextPointWithDistance(Point from, int64_t dist, ConstPolygonRef poly, int start_idx, int poly_start_idx, GivenDistPoint& result)
1042 {
1043
1044 Point prev_poly_point = poly[(start_idx + poly_start_idx) % poly.size()];
1045
1046 for (unsigned int prev_idx = start_idx; prev_idx < poly.size(); prev_idx++)
1047 {
1048 int next_idx = (prev_idx + 1 + poly_start_idx) % poly.size(); // last checked segment is between last point in poly and poly[0]...
1049 const Point& next_poly_point = poly[next_idx];
1050 if ( !shorterThen(next_poly_point - from, dist) )
1051 {
1052 /*
1053 * x r
1054 * p.---------+---+------------.n
1055 * L| /
1056 * | / dist
1057 * |/
1058 * f.
1059 *
1060 * f=from
1061 * p=prev_poly_point
1062 * n=next_poly_point
1063 * x= f projected on pn
1064 * r=result point at distance [dist] from f
1065 */
1066
1067 Point pn = next_poly_point - prev_poly_point;
1068
1069 if (shorterThen(pn, 100)) // when precision is limited
1070 {
1071 Point middle = (next_poly_point + prev_poly_point) / 2;
1072 int64_t dist_to_middle = vSize(from - middle);
1073 if (dist_to_middle - dist < 100 && dist_to_middle - dist > -100)
1074 {
1075 result.location = middle;
1076 result.pos = prev_idx;
1077 return true;
1078 } else
1079 {
1080 prev_poly_point = next_poly_point;
1081 continue;
1082 }
1083 }
1084
1085 Point pf = from - prev_poly_point;
1086 Point px = dot(pf, pn) / vSize(pn) * pn / vSize(pn);
1087 Point xf = pf - px;
1088
1089 if (!shorterThen(xf, dist)) // line lies wholly further than pn
1090 {
1091 prev_poly_point = next_poly_point;
1092 continue;
1093
1094 }
1095
1096 int64_t xr_dist = std::sqrt(dist*dist - vSize2(xf)); // inverse Pythagoras
1097
1098 if (vSize(pn - px) - xr_dist < 1) // r lies beyond n
1099 {
1100 prev_poly_point = next_poly_point;
1101 continue;
1102 }
1103
1104 Point xr = xr_dist * pn / vSize(pn);
1105 Point pr = px + xr;
1106
1107 result.location = prev_poly_point + pr;
1108 result.pos = prev_idx;
1109 return true;
1110 }
1111 prev_poly_point = next_poly_point;
1112 }
1113 return false;
1114 }
1115
1116
getNextParallelIntersection(const ClosestPolygonPoint & start,const Point & line_to,const coord_t dist,const bool forward)1117 std::optional<ClosestPolygonPoint> PolygonUtils::getNextParallelIntersection(const ClosestPolygonPoint& start, const Point& line_to, const coord_t dist, const bool forward)
1118 {
1119 // <--o--t-----y----< poly 1
1120 // : :
1121 // >---o :====>:shift
1122 // \: :
1123 // s x
1124 // \ :
1125 // \ :
1126 // o--r-----> poly 2
1127 // s=start
1128 // r=result
1129 // t=line_to
1130
1131 ConstPolygonRef poly = *start.poly;
1132 const Point s = start.p();
1133 const Point t = line_to;
1134
1135 const Point st = t - s;
1136 const Point shift = normal(turn90CCW(st), dist);
1137
1138 Point prev_vert = s;
1139 coord_t prev_projected = 0;
1140 for (unsigned int next_point_nr = 0; next_point_nr < poly.size(); next_point_nr++)
1141 {
1142 const unsigned int next_point_idx =
1143 forward ?
1144 (start.point_idx + 1 + next_point_nr) % poly.size()
1145 : (static_cast<size_t>(start.point_idx) - next_point_nr + poly.size()) % poly.size(); // cast in order to accomodate subtracting
1146 const Point next_vert = poly[next_point_idx];
1147 const Point so = next_vert - s;
1148 const coord_t projected = dot(shift, so) / dist;
1149 if (std::abs(projected) > dist)
1150 { // segment crosses the line through xy (or the one on the other side of st)
1151 const Point segment_vector = next_vert - prev_vert;
1152 const coord_t segment_length = vSize(segment_vector);
1153 const coord_t projected_segment_length = std::abs(projected - prev_projected);
1154 const int16_t sign = (projected > 0) ? 1 : -1;
1155 const coord_t projected_inter_segment_length = dist - sign * prev_projected; // add the prev_projected to dist if it is projected to the other side of the input line than where the intersection occurs.
1156 const coord_t inter_segment_length = segment_length * projected_inter_segment_length / projected_segment_length;
1157 const Point intersection = prev_vert + normal(next_vert - prev_vert, inter_segment_length);
1158
1159 size_t vert_before_idx = next_point_idx;
1160 if (forward)
1161 {
1162 vert_before_idx = (next_point_idx > 0) ? vert_before_idx - 1 : poly.size() - 1;
1163 }
1164 assert(vert_before_idx < poly.size());
1165 return ClosestPolygonPoint(intersection, vert_before_idx, poly);
1166 }
1167
1168 prev_vert = next_vert;
1169 prev_projected = projected;
1170 }
1171
1172 return std::optional<ClosestPolygonPoint>();
1173 }
1174
1175
polygonCollidesWithLineSegment(const Point from,const Point to,const LocToLineGrid & loc_to_line,PolygonsPointIndex * collision_result)1176 bool PolygonUtils::polygonCollidesWithLineSegment(const Point from, const Point to, const LocToLineGrid& loc_to_line, PolygonsPointIndex* collision_result)
1177 {
1178 bool ret = false;
1179 Point diff = to - from;
1180 if (vSize2(diff) < 2)
1181 { // transformation matrix would fail
1182 return false;
1183 }
1184
1185 PointMatrix transformation_matrix = PointMatrix(diff);
1186 Point transformed_from = transformation_matrix.apply(from);
1187 Point transformed_to = transformation_matrix.apply(to);
1188
1189 PolygonsPointIndex result;
1190
1191 std::function<bool (const PolygonsPointIndex&)> process_elem_func =
1192 [transformed_from, transformed_to, &transformation_matrix, &result, &ret]
1193 (const PolygonsPointIndex& line_start)
1194 {
1195 Point p0 = transformation_matrix.apply(line_start.p());
1196 Point p1 = transformation_matrix.apply(line_start.next().p());
1197
1198 if (LinearAlg2D::lineSegmentsCollide(transformed_from, transformed_to, p0, p1))
1199 {
1200 result = line_start;
1201 ret = true;
1202 return false;
1203 }
1204 return true;
1205 };
1206 loc_to_line.processLine(std::make_pair(from, to), process_elem_func);
1207
1208 if (collision_result)
1209 {
1210 *collision_result = result;
1211 }
1212 return ret;
1213 }
1214
polygonCollidesWithLineSegment(ConstPolygonRef poly,const Point & transformed_startPoint,const Point & transformed_endPoint,PointMatrix transformation_matrix)1215 bool PolygonUtils::polygonCollidesWithLineSegment(ConstPolygonRef poly, const Point& transformed_startPoint, const Point& transformed_endPoint, PointMatrix transformation_matrix)
1216 {
1217 Point p0 = transformation_matrix.apply(poly.back());
1218 for(Point p1_ : poly)
1219 {
1220 Point p1 = transformation_matrix.apply(p1_);
1221 if (LinearAlg2D::lineSegmentsCollide(transformed_startPoint, transformed_endPoint, p0, p1))
1222 {
1223 return true;
1224 }
1225 p0 = p1;
1226 }
1227 return false;
1228 }
1229
polygonCollidesWithLineSegment(ConstPolygonRef poly,const Point & startPoint,const Point & endPoint)1230 bool PolygonUtils::polygonCollidesWithLineSegment(ConstPolygonRef poly, const Point& startPoint, const Point& endPoint)
1231 {
1232 Point diff = endPoint - startPoint;
1233
1234 PointMatrix transformation_matrix = PointMatrix(diff);
1235 Point transformed_startPoint = transformation_matrix.apply(startPoint);
1236 Point transformed_endPoint = transformation_matrix.apply(endPoint);
1237
1238 return PolygonUtils::polygonCollidesWithLineSegment(poly, transformed_startPoint, transformed_endPoint, transformation_matrix);
1239 }
1240
polygonCollidesWithLineSegment(const Polygons & polys,const Point & transformed_startPoint,const Point & transformed_endPoint,PointMatrix transformation_matrix)1241 bool PolygonUtils::polygonCollidesWithLineSegment(const Polygons& polys, const Point& transformed_startPoint, const Point& transformed_endPoint, PointMatrix transformation_matrix)
1242 {
1243 for (ConstPolygonRef poly : polys)
1244 {
1245 if (poly.size() == 0) { continue; }
1246 if (PolygonUtils::polygonCollidesWithLineSegment(poly, transformed_startPoint, transformed_endPoint, transformation_matrix))
1247 {
1248 return true;
1249 }
1250 }
1251
1252 return false;
1253 }
1254
1255
polygonCollidesWithLineSegment(const Polygons & polys,const Point & startPoint,const Point & endPoint)1256 bool PolygonUtils::polygonCollidesWithLineSegment(const Polygons& polys, const Point& startPoint, const Point& endPoint)
1257 {
1258 Point diff = endPoint - startPoint;
1259
1260 PointMatrix transformation_matrix = PointMatrix(diff);
1261 Point transformed_startPoint = transformation_matrix.apply(startPoint);
1262 Point transformed_endPoint = transformation_matrix.apply(endPoint);
1263
1264 return polygonCollidesWithLineSegment(polys, transformed_startPoint, transformed_endPoint, transformation_matrix);
1265 }
1266
polygonsIntersect(const ConstPolygonRef & poly_a,const ConstPolygonRef & poly_b)1267 bool PolygonUtils::polygonsIntersect(const ConstPolygonRef& poly_a, const ConstPolygonRef& poly_b)
1268 {
1269 // only do the full intersection when the polys' BBs overlap
1270 AABB bba(poly_a);
1271 AABB bbb(poly_b);
1272 return bba.hit(bbb) && poly_a.intersection(poly_b).size() > 0;
1273 }
1274
polygonOutlinesAdjacent(const ConstPolygonRef inner_poly,const ConstPolygonRef outer_poly,const coord_t max_gap)1275 bool PolygonUtils::polygonOutlinesAdjacent(const ConstPolygonRef inner_poly, const ConstPolygonRef outer_poly, const coord_t max_gap)
1276 {
1277 //Heuristic check if their AABBs are near first.
1278 AABB inner_aabb(inner_poly);
1279 AABB outer_aabb(outer_poly);
1280 inner_aabb.max += Point(max_gap, max_gap); //Expand one of them by way of a "distance" by checking intersection with the expanded rectangle.
1281 inner_aabb.min -= Point(max_gap, max_gap);
1282 if (!inner_aabb.hit(outer_aabb))
1283 {
1284 return false;
1285 }
1286
1287 //Heuristic says they are near. Now check for real.
1288 const coord_t max_gap2 = max_gap * max_gap;
1289 const unsigned outer_poly_size = outer_poly.size();
1290 for (unsigned line_index = 0; line_index < outer_poly_size; ++line_index)
1291 {
1292 const Point lp0 = outer_poly[line_index];
1293 const Point lp1 = outer_poly[(line_index + 1) % outer_poly_size];
1294 for (Point inner_poly_point : inner_poly)
1295 {
1296 if (LinearAlg2D::getDist2FromLineSegment(lp0, inner_poly_point, lp1) < max_gap2)
1297 {
1298 return true;
1299 }
1300 }
1301 }
1302 return false;
1303 }
1304
findAdjacentPolygons(std::vector<unsigned> & adjacent_poly_indices,const ConstPolygonRef & poly,const std::vector<ConstPolygonPointer> & possible_adjacent_polys,const coord_t max_gap)1305 void PolygonUtils::findAdjacentPolygons(std::vector<unsigned>& adjacent_poly_indices, const ConstPolygonRef& poly, const std::vector<ConstPolygonPointer>& possible_adjacent_polys, const coord_t max_gap)
1306 {
1307 // given a polygon, and a vector of polygons, return a vector containing the indices of the polygons that are adjacent to the given polygon
1308 for (unsigned poly_idx = 0; poly_idx < possible_adjacent_polys.size(); ++poly_idx)
1309 {
1310 if (polygonOutlinesAdjacent(poly, *possible_adjacent_polys[poly_idx], max_gap) ||
1311 polygonOutlinesAdjacent(*possible_adjacent_polys[poly_idx], poly, max_gap))
1312 {
1313 adjacent_poly_indices.push_back(poly_idx);
1314 }
1315 }
1316 }
1317
relativeHammingDistance(const Polygons & poly_a,const Polygons & poly_b)1318 double PolygonUtils::relativeHammingDistance(const Polygons& poly_a, const Polygons& poly_b)
1319 {
1320 const double area_a = std::abs(poly_a.area());
1321 const double area_b = std::abs(poly_b.area());
1322 const double total_area = area_a + area_b;
1323
1324 //If the total area is 0.0, we'd get a division by zero. Instead, only return 0.0 if they are exactly equal.
1325 constexpr bool borders_allowed = true;
1326 if(total_area == 0.0)
1327 {
1328 for(const ConstPolygonRef& polygon_a : poly_a)
1329 {
1330 for(Point point : polygon_a)
1331 {
1332 if(!poly_b.inside(point, borders_allowed))
1333 {
1334 return 1.0;
1335 }
1336 }
1337 }
1338 for(const ConstPolygonRef& polygon_b : poly_b)
1339 {
1340 for(Point point : polygon_b)
1341 {
1342 if(!poly_a.inside(point, borders_allowed))
1343 {
1344 return 1.0;
1345 }
1346 }
1347 }
1348 return 0.0; //All points are inside the other polygon, regardless of where the vertices are along the edges.
1349 }
1350
1351 const Polygons symmetric_difference = poly_a.xorPolygons(poly_b);
1352 const double hamming_distance = symmetric_difference.area();
1353 return hamming_distance / total_area;
1354 }
1355
1356 }//namespace cura
1357