clipper2 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +165 -0
  4. data/examples/larb_transform.rb +17 -0
  5. data/examples/rugl_mesh.rb +33 -0
  6. data/examples/svg_boolean.rb +28 -0
  7. data/ext/clipper2/CMakeLists.txt +27 -0
  8. data/ext/clipper2/clipper2_native.cpp +2 -0
  9. data/ext/clipper2/extconf.rb +27 -0
  10. data/ext/clipper2/generate_bindings.rb +73 -0
  11. data/ext/clipper2/memory_check.cpp +48 -0
  12. data/ext/clipper2/vendor/LICENSE +23 -0
  13. data/ext/clipper2/vendor/UPSTREAM.md +11 -0
  14. data/ext/clipper2/vendor/include/clipper2/clipper.core.h +1159 -0
  15. data/ext/clipper2/vendor/include/clipper2/clipper.engine.h +635 -0
  16. data/ext/clipper2/vendor/include/clipper2/clipper.export.h +851 -0
  17. data/ext/clipper2/vendor/include/clipper2/clipper.h +796 -0
  18. data/ext/clipper2/vendor/include/clipper2/clipper.minkowski.h +117 -0
  19. data/ext/clipper2/vendor/include/clipper2/clipper.offset.h +125 -0
  20. data/ext/clipper2/vendor/include/clipper2/clipper.rectclip.h +80 -0
  21. data/ext/clipper2/vendor/include/clipper2/clipper.triangulation.h +27 -0
  22. data/ext/clipper2/vendor/include/clipper2/clipper.version.h +6 -0
  23. data/ext/clipper2/vendor/src/clipper.engine.cpp +3152 -0
  24. data/ext/clipper2/vendor/src/clipper.offset.cpp +661 -0
  25. data/ext/clipper2/vendor/src/clipper.rectclip.cpp +1027 -0
  26. data/ext/clipper2/vendor/src/clipper.triangulation.cpp +1221 -0
  27. data/lib/clipper2/api.rb +45 -0
  28. data/lib/clipper2/c_paths64.rb +80 -0
  29. data/lib/clipper2/earcut/engine.rb +191 -0
  30. data/lib/clipper2/earcut/geometry.rb +100 -0
  31. data/lib/clipper2/earcut/node.rb +18 -0
  32. data/lib/clipper2/earcut/ring.rb +94 -0
  33. data/lib/clipper2/earcut.rb +18 -0
  34. data/lib/clipper2/errors.rb +19 -0
  35. data/lib/clipper2/generated.rb +62 -0
  36. data/lib/clipper2/native.rb +38 -0
  37. data/lib/clipper2/native_memory.rb +64 -0
  38. data/lib/clipper2/operations.rb +206 -0
  39. data/lib/clipper2/path.rb +171 -0
  40. data/lib/clipper2/path_simplifier.rb +61 -0
  41. data/lib/clipper2/paths.rb +130 -0
  42. data/lib/clipper2/quantizer.rb +48 -0
  43. data/lib/clipper2/triangulation.rb +103 -0
  44. data/lib/clipper2/version.rb +5 -0
  45. data/lib/clipper2.rb +34 -0
  46. data/licenses/CLIPPER2-BSL-1.0.txt +23 -0
  47. data/licenses/EARCUT-ISC.txt +15 -0
  48. metadata +113 -0
@@ -0,0 +1,1027 @@
1
+ /*******************************************************************************
2
+ * Author : Angus Johnson *
3
+ * Date : 11 October 2025 *
4
+ * Website : https://www.angusj.com *
5
+ * Copyright : Angus Johnson 2010-2025 *
6
+ * Purpose : FAST rectangular clipping *
7
+ * License : https://www.boost.org/LICENSE_1_0.txt *
8
+ *******************************************************************************/
9
+
10
+ #include "clipper2/clipper.h"
11
+ #include "clipper2/clipper.rectclip.h"
12
+
13
+ namespace Clipper2Lib {
14
+
15
+ //------------------------------------------------------------------------------
16
+ // Miscellaneous methods
17
+ //------------------------------------------------------------------------------
18
+
19
+ inline bool Path1ContainsPath2(const Path64& path1, const Path64& path2)
20
+ {
21
+ int io_count = 0;
22
+ // precondition: no (significant) overlap
23
+ for (const Point64& pt : path2)
24
+ {
25
+ PointInPolygonResult pip = PointInPolygon(pt, path1);
26
+ switch (pip)
27
+ {
28
+ case PointInPolygonResult::IsOutside: ++io_count; break;
29
+ case PointInPolygonResult::IsInside: --io_count; break;
30
+ default: continue;
31
+ }
32
+ if (std::abs(io_count) > 1) break;
33
+ }
34
+ return io_count <= 0;
35
+ }
36
+
37
+ inline bool GetLocation(const Rect64& rec,
38
+ const Point64& pt, Location& loc)
39
+ {
40
+ if (pt.x == rec.left && pt.y >= rec.top && pt.y <= rec.bottom)
41
+ {
42
+ loc = Location::Left;
43
+ return false;
44
+ }
45
+ else if (pt.x == rec.right && pt.y >= rec.top && pt.y <= rec.bottom)
46
+ {
47
+ loc = Location::Right;
48
+ return false;
49
+ }
50
+ else if (pt.y == rec.top && pt.x >= rec.left && pt.x <= rec.right)
51
+ {
52
+ loc = Location::Top;
53
+ return false;
54
+ }
55
+ else if (pt.y == rec.bottom && pt.x >= rec.left && pt.x <= rec.right)
56
+ {
57
+ loc = Location::Bottom;
58
+ return false;
59
+ }
60
+ else if (pt.x < rec.left) loc = Location::Left;
61
+ else if (pt.x > rec.right) loc = Location::Right;
62
+ else if (pt.y < rec.top) loc = Location::Top;
63
+ else if (pt.y > rec.bottom) loc = Location::Bottom;
64
+ else loc = Location::Inside;
65
+ return true;
66
+ }
67
+
68
+ inline bool IsHorizontal(const Point64& pt1, const Point64& pt2)
69
+ {
70
+ return pt1.y == pt2.y;
71
+ }
72
+
73
+ bool GetSegmentIntersection(const Point64& p1,
74
+ const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip)
75
+ {
76
+ int res1 = CrossProductSign(p1, p3, p4);
77
+ int res2 = CrossProductSign(p2, p3, p4);
78
+ if (res1 == 0)
79
+ {
80
+ ip = p1;
81
+ if (res2 == 0) return false; // segments are collinear
82
+ else if (p1 == p3 || p1 == p4) return true;
83
+ //else if (p2 == p3 || p2 == p4) { ip = p2; return true; }
84
+ else if (IsHorizontal(p3, p4)) return ((p1.x > p3.x) == (p1.x < p4.x));
85
+ else return ((p1.y > p3.y) == (p1.y < p4.y));
86
+ }
87
+ else if (res2 == 0)
88
+ {
89
+ ip = p2;
90
+ if (p2 == p3 || p2 == p4) return true;
91
+ else if (IsHorizontal(p3, p4)) return ((p2.x > p3.x) == (p2.x < p4.x));
92
+ else return ((p2.y > p3.y) == (p2.y < p4.y));
93
+ }
94
+ if ((res1 > 0) == (res2 > 0)) return false;
95
+
96
+ int res3 = CrossProductSign(p3, p1, p2);
97
+ int res4 = CrossProductSign(p4, p1, p2);
98
+ if (res3 == 0)
99
+ {
100
+ ip = p3;
101
+ if (p3 == p1 || p3 == p2) return true;
102
+ else if (IsHorizontal(p1, p2)) return ((p3.x > p1.x) == (p3.x < p2.x));
103
+ else return ((p3.y > p1.y) == (p3.y < p2.y));
104
+ }
105
+ else if (res4 == 0)
106
+ {
107
+ ip = p4;
108
+ if (p4 == p1 || p4 == p2) return true;
109
+ else if (IsHorizontal(p1, p2)) return ((p4.x > p1.x) == (p4.x < p2.x));
110
+ else return ((p4.y > p1.y) == (p4.y < p2.y));
111
+ }
112
+ if ((res3 > 0) == (res4 > 0)) return false;
113
+
114
+ // segments must intersect to get here
115
+ return GetLineIntersectPt(p1, p2, p3, p4, ip);
116
+ }
117
+
118
+ inline bool GetIntersection(const Path64& rectPath,
119
+ const Point64& p, const Point64& p2, Location& loc, Point64& ip)
120
+ {
121
+ // gets the intersection closest to 'p'
122
+ // when Result = false, loc will remain unchanged
123
+ switch (loc)
124
+ {
125
+ case Location::Left:
126
+ if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) return true;
127
+ else if ((p.y < rectPath[0].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip))
128
+ {
129
+ loc = Location::Top;
130
+ return true;
131
+ }
132
+ else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip))
133
+ {
134
+ loc = Location::Bottom;
135
+ return true;
136
+ }
137
+ else return false;
138
+
139
+ case Location::Top:
140
+ if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) return true;
141
+ else if ((p.x < rectPath[0].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip))
142
+ {
143
+ loc = Location::Left;
144
+ return true;
145
+ }
146
+ else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip))
147
+ {
148
+ loc = Location::Right;
149
+ return true;
150
+ }
151
+ else return false;
152
+
153
+ case Location::Right:
154
+ if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) return true;
155
+ else if ((p.y < rectPath[1].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip))
156
+ {
157
+ loc = Location::Top;
158
+ return true;
159
+ }
160
+ else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip))
161
+ {
162
+ loc = Location::Bottom;
163
+ return true;
164
+ }
165
+ else return false;
166
+
167
+ case Location::Bottom:
168
+ if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) return true;
169
+ else if ((p.x < rectPath[3].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip))
170
+ {
171
+ loc = Location::Left;
172
+ return true;
173
+ }
174
+ else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip))
175
+ {
176
+ loc = Location::Right;
177
+ return true;
178
+ }
179
+ else return false;
180
+
181
+ default: // loc == rInside
182
+ if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip))
183
+ {
184
+ loc = Location::Left;
185
+ return true;
186
+ }
187
+ else if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip))
188
+ {
189
+ loc = Location::Top;
190
+ return true;
191
+ }
192
+ else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip))
193
+ {
194
+ loc = Location::Right;
195
+ return true;
196
+ }
197
+ else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip))
198
+ {
199
+ loc = Location::Bottom;
200
+ return true;
201
+ }
202
+ else return false;
203
+ }
204
+ }
205
+
206
+ inline Location GetAdjacentLocation(Location loc, bool isClockwise)
207
+ {
208
+ int delta = (isClockwise) ? 1 : 3;
209
+ return static_cast<Location>((static_cast<int>(loc) + delta) % 4);
210
+ }
211
+
212
+ inline bool HeadingClockwise(Location prev, Location curr)
213
+ {
214
+ return (static_cast<int>(prev) + 1) % 4 == static_cast<int>(curr);
215
+ }
216
+
217
+ inline bool AreOpposites(Location prev, Location curr)
218
+ {
219
+ return abs(static_cast<int>(prev) - static_cast<int>(curr)) == 2;
220
+ }
221
+
222
+ inline bool IsClockwise(Location prev, Location curr,
223
+ const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp)
224
+ {
225
+ if (AreOpposites(prev, curr))
226
+ return CrossProductSign(prev_pt, rect_mp, curr_pt) < 0;
227
+ else
228
+ return HeadingClockwise(prev, curr);
229
+ }
230
+
231
+ inline OutPt2* UnlinkOp(OutPt2* op)
232
+ {
233
+ if (op->next == op) return nullptr;
234
+ op->prev->next = op->next;
235
+ op->next->prev = op->prev;
236
+ return op->next;
237
+ }
238
+
239
+ inline OutPt2* UnlinkOpBack(OutPt2* op)
240
+ {
241
+ if (op->next == op) return nullptr;
242
+ op->prev->next = op->next;
243
+ op->next->prev = op->prev;
244
+ return op->prev;
245
+ }
246
+
247
+ inline uint32_t GetEdgesForPt(const Point64& pt, const Rect64& rec)
248
+ {
249
+ uint32_t result = 0;
250
+ if (pt.x == rec.left) result = 1;
251
+ else if (pt.x == rec.right) result = 4;
252
+ if (pt.y == rec.top) result += 2;
253
+ else if (pt.y == rec.bottom) result += 8;
254
+ return result;
255
+ }
256
+
257
+ inline bool IsHeadingClockwise(const Point64& pt1, const Point64& pt2, int edgeIdx)
258
+ {
259
+ switch (edgeIdx)
260
+ {
261
+ case 0: return pt2.y < pt1.y;
262
+ case 1: return pt2.x > pt1.x;
263
+ case 2: return pt2.y > pt1.y;
264
+ default: return pt2.x < pt1.x;
265
+ }
266
+ }
267
+
268
+ inline bool HasHorzOverlap(const Point64& left1, const Point64& right1,
269
+ const Point64& left2, const Point64& right2)
270
+ {
271
+ return (left1.x < right2.x) && (right1.x > left2.x);
272
+ }
273
+
274
+ inline bool HasVertOverlap(const Point64& top1, const Point64& bottom1,
275
+ const Point64& top2, const Point64& bottom2)
276
+ {
277
+ return (top1.y < bottom2.y) && (bottom1.y > top2.y);
278
+ }
279
+
280
+ inline void AddToEdge(OutPt2List& edge, OutPt2* op)
281
+ {
282
+ if (op->edge) return;
283
+ op->edge = &edge;
284
+ edge.emplace_back(op);
285
+ }
286
+
287
+ inline void UncoupleEdge(OutPt2* op)
288
+ {
289
+ if (!op->edge) return;
290
+ for (size_t i = 0; i < op->edge->size(); ++i)
291
+ {
292
+ OutPt2* op2 = (*op->edge)[i];
293
+ if (op2 == op)
294
+ {
295
+ (*op->edge)[i] = nullptr;
296
+ break;
297
+ }
298
+ }
299
+ op->edge = nullptr;
300
+ }
301
+
302
+ inline void SetNewOwner(OutPt2* op, size_t new_idx)
303
+ {
304
+ op->owner_idx = new_idx;
305
+ OutPt2* op2 = op->next;
306
+ while (op2 != op)
307
+ {
308
+ op2->owner_idx = new_idx;
309
+ op2 = op2->next;
310
+ }
311
+ }
312
+
313
+ //----------------------------------------------------------------------------
314
+ // RectClip64
315
+ //----------------------------------------------------------------------------
316
+
317
+ OutPt2* RectClip64::Add(Point64 pt, bool start_new)
318
+ {
319
+ // this method is only called by InternalExecute.
320
+ // Later splitting & rejoining won't create additional op's,
321
+ // though they will change the (non-storage) results_ count.
322
+ size_t curr_idx = results_.size();
323
+ OutPt2* result;
324
+ if (curr_idx == 0 || start_new)
325
+ {
326
+ result = &op_container_.emplace_back(OutPt2());
327
+ result->pt = pt;
328
+ result->next = result;
329
+ result->prev = result;
330
+ results_.emplace_back(result);
331
+ }
332
+ else
333
+ {
334
+ --curr_idx;
335
+ OutPt2* prevOp = results_[curr_idx];
336
+ if (prevOp->pt == pt) return prevOp;
337
+ result = &op_container_.emplace_back(OutPt2());
338
+ result->owner_idx = curr_idx;
339
+ result->pt = pt;
340
+ result->next = prevOp->next;
341
+ prevOp->next->prev = result;
342
+ prevOp->next = result;
343
+ result->prev = prevOp;
344
+ results_[curr_idx] = result;
345
+ }
346
+ return result;
347
+ }
348
+
349
+ void RectClip64::AddCorner(Location prev, Location curr)
350
+ {
351
+ if (HeadingClockwise(prev, curr))
352
+ Add(rect_as_path_[static_cast<size_t>(prev)]);
353
+ else
354
+ Add(rect_as_path_[static_cast<size_t>(curr)]);
355
+ }
356
+
357
+ void RectClip64::AddCorner(Location& loc, bool isClockwise)
358
+ {
359
+ if (isClockwise)
360
+ {
361
+ Add(rect_as_path_[static_cast<size_t>(loc)]);
362
+ loc = GetAdjacentLocation(loc, true);
363
+ }
364
+ else
365
+ {
366
+ loc = GetAdjacentLocation(loc, false);
367
+ Add(rect_as_path_[static_cast<size_t>(loc)]);
368
+ }
369
+ }
370
+
371
+ void RectClip64::GetNextLocation(const Path64& path,
372
+ Location& loc, size_t& i, size_t highI)
373
+ {
374
+ switch (loc)
375
+ {
376
+ case Location::Left:
377
+ while (i <= highI && path[i].x <= rect_.left) ++i;
378
+ if (i > highI) break;
379
+ else if (path[i].x >= rect_.right) loc = Location::Right;
380
+ else if (path[i].y <= rect_.top) loc = Location::Top;
381
+ else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
382
+ else loc = Location::Inside;
383
+ break;
384
+
385
+ case Location::Top:
386
+ while (i <= highI && path[i].y <= rect_.top) ++i;
387
+ if (i > highI) break;
388
+ else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
389
+ else if (path[i].x <= rect_.left) loc = Location::Left;
390
+ else if (path[i].x >= rect_.right) loc = Location::Right;
391
+ else loc = Location::Inside;
392
+ break;
393
+
394
+ case Location::Right:
395
+ while (i <= highI && path[i].x >= rect_.right) ++i;
396
+ if (i > highI) break;
397
+ else if (path[i].x <= rect_.left) loc = Location::Left;
398
+ else if (path[i].y <= rect_.top) loc = Location::Top;
399
+ else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
400
+ else loc = Location::Inside;
401
+ break;
402
+
403
+ case Location::Bottom:
404
+ while (i <= highI && path[i].y >= rect_.bottom) ++i;
405
+ if (i > highI) break;
406
+ else if (path[i].y <= rect_.top) loc = Location::Top;
407
+ else if (path[i].x <= rect_.left) loc = Location::Left;
408
+ else if (path[i].x >= rect_.right) loc = Location::Right;
409
+ else loc = Location::Inside;
410
+ break;
411
+
412
+ case Location::Inside:
413
+ while (i <= highI)
414
+ {
415
+ if (path[i].x < rect_.left) loc = Location::Left;
416
+ else if (path[i].x > rect_.right) loc = Location::Right;
417
+ else if (path[i].y > rect_.bottom) loc = Location::Bottom;
418
+ else if (path[i].y < rect_.top) loc = Location::Top;
419
+ else { Add(path[i]); ++i; continue; }
420
+ break; //inner loop
421
+ }
422
+ break;
423
+ } //switch
424
+ }
425
+
426
+ bool StartLocsAreClockwise(const std::vector<Location>& startlocs)
427
+ {
428
+ int result = 0;
429
+ for (size_t i = 1; i < startlocs.size(); ++i)
430
+ {
431
+ int d = static_cast<int>(startlocs[i]) - static_cast<int>(startlocs[i - 1]);
432
+ switch (d)
433
+ {
434
+ case -1: result -= 1; break;
435
+ case 1: result += 1; break;
436
+ case -3: result += 1; break;
437
+ case 3: result -= 1; break;
438
+ }
439
+ }
440
+ return result > 0;
441
+ }
442
+
443
+ void RectClip64::ExecuteInternal(const Path64& path)
444
+ {
445
+ if (path.size() < 1)
446
+ return;
447
+
448
+ size_t highI = path.size() - 1;
449
+ Location prev = Location::Inside, loc;
450
+ Location crossing_loc = Location::Inside;
451
+ Location first_cross_ = Location::Inside;
452
+ if (!GetLocation(rect_, path[highI], loc))
453
+ {
454
+ size_t i = highI;
455
+ while (i > 0 && !GetLocation(rect_, path[i - 1], prev))
456
+ --i;
457
+ if (i == 0)
458
+ {
459
+ // all of path must be inside fRect
460
+ for (const auto& pt : path) Add(pt);
461
+ return;
462
+ }
463
+ if (prev == Location::Inside) loc = Location::Inside;
464
+ }
465
+ Location starting_loc = loc;
466
+
467
+ ///////////////////////////////////////////////////
468
+ size_t i = 0;
469
+ while (i <= highI)
470
+ {
471
+ prev = loc;
472
+ Location crossing_prev = crossing_loc;
473
+
474
+ GetNextLocation(path, loc, i, highI);
475
+
476
+ if (i > highI) break;
477
+ Point64 ip, ip2;
478
+ Point64 prev_pt = (i) ?
479
+ path[static_cast<size_t>(i - 1)] :
480
+ path[highI];
481
+
482
+ crossing_loc = loc;
483
+ if (!GetIntersection(rect_as_path_,
484
+ path[i], prev_pt, crossing_loc, ip))
485
+ {
486
+ // ie remaining outside
487
+ if (crossing_prev == Location::Inside)
488
+ {
489
+ bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_);
490
+ do {
491
+ start_locs_.emplace_back(prev);
492
+ prev = GetAdjacentLocation(prev, isClockw);
493
+ } while (prev != loc);
494
+ crossing_loc = crossing_prev; // still not crossed
495
+ }
496
+ else if (prev != Location::Inside && prev != loc)
497
+ {
498
+ bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_);
499
+ do {
500
+ AddCorner(prev, isClockw);
501
+ } while (prev != loc);
502
+ }
503
+ ++i;
504
+ continue;
505
+ }
506
+
507
+ ////////////////////////////////////////////////////
508
+ // we must be crossing the rect boundary to get here
509
+ ////////////////////////////////////////////////////
510
+
511
+ if (loc == Location::Inside) // path must be entering rect
512
+ {
513
+ if (first_cross_ == Location::Inside)
514
+ {
515
+ first_cross_ = crossing_loc;
516
+ start_locs_.emplace_back(prev);
517
+ }
518
+ else if (prev != crossing_loc)
519
+ {
520
+ bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], rect_mp_);
521
+ do {
522
+ AddCorner(prev, isClockw);
523
+ } while (prev != crossing_loc);
524
+ }
525
+ }
526
+ else if (prev != Location::Inside)
527
+ {
528
+ // passing right through rect. 'ip' here will be the second
529
+ // intersect pt but we'll also need the first intersect pt (ip2)
530
+ loc = prev;
531
+ GetIntersection(rect_as_path_, prev_pt, path[i], loc, ip2);
532
+ if (crossing_prev != Location::Inside && crossing_prev != loc) //579
533
+ AddCorner(crossing_prev, loc);
534
+
535
+ if (first_cross_ == Location::Inside)
536
+ {
537
+ first_cross_ = loc;
538
+ start_locs_.emplace_back(prev);
539
+ }
540
+
541
+ loc = crossing_loc;
542
+ Add(ip2);
543
+ if (ip == ip2)
544
+ {
545
+ // it's very likely that path[i] is on rect
546
+ GetLocation(rect_, path[i], loc);
547
+ AddCorner(crossing_loc, loc);
548
+ crossing_loc = loc;
549
+ continue;
550
+ }
551
+ }
552
+ else // path must be exiting rect
553
+ {
554
+ loc = crossing_loc;
555
+ if (first_cross_ == Location::Inside)
556
+ first_cross_ = crossing_loc;
557
+ }
558
+
559
+ Add(ip);
560
+
561
+ } //while i <= highI
562
+ ///////////////////////////////////////////////////
563
+
564
+ if (first_cross_ == Location::Inside)
565
+ {
566
+ // path never intersects
567
+ if (starting_loc != Location::Inside)
568
+ {
569
+ // path is outside rect
570
+ // but being outside, it still may not contain rect
571
+ if (path_bounds_.Contains(rect_) &&
572
+ Path1ContainsPath2(path, rect_as_path_))
573
+ {
574
+ // yep, the path does fully contain rect
575
+ // so add rect to the solution
576
+ bool is_clockwise_path = StartLocsAreClockwise(start_locs_);
577
+ for (size_t j = 0; j < 4; ++j)
578
+ {
579
+ size_t k = is_clockwise_path ? j : 3 - j; // reverses result path
580
+ Add(rect_as_path_[k]);
581
+ // we may well need to do some splitting later, so
582
+ AddToEdge(edges_[k * 2], results_[0]);
583
+ }
584
+ }
585
+ }
586
+ }
587
+ else if (loc != Location::Inside &&
588
+ (loc != first_cross_ || start_locs_.size() > 2))
589
+ {
590
+ if (start_locs_.size() > 0)
591
+ {
592
+ prev = loc;
593
+ for (auto loc2 : start_locs_)
594
+ {
595
+ if (prev == loc2) continue;
596
+ AddCorner(prev, HeadingClockwise(prev, loc2));
597
+ prev = loc2;
598
+ }
599
+ loc = prev;
600
+ }
601
+ if (loc != first_cross_)
602
+ AddCorner(loc, HeadingClockwise(loc, first_cross_));
603
+ }
604
+ }
605
+
606
+ void RectClip64::CheckEdges()
607
+ {
608
+ for (size_t i = 0; i < results_.size(); ++i)
609
+ {
610
+ OutPt2* op = results_[i];
611
+ if (!op) continue;
612
+ OutPt2* op2 = op;
613
+ do
614
+ {
615
+ if (IsCollinear(op2->prev->pt, op2->pt, op2->next->pt))
616
+ {
617
+ if (op2 == op)
618
+ {
619
+ op2 = UnlinkOpBack(op2);
620
+ if (!op2) break;
621
+ op = op2->prev;
622
+ }
623
+ else
624
+ {
625
+ op2 = UnlinkOpBack(op2);
626
+ if (!op2) break;
627
+ }
628
+ }
629
+ else
630
+ op2 = op2->next;
631
+ } while (op2 != op);
632
+
633
+ if (!op2)
634
+ {
635
+ results_[i] = nullptr;
636
+ continue;
637
+ }
638
+ results_[i] = op; // safety first
639
+
640
+ uint32_t edgeSet1 = GetEdgesForPt(op->prev->pt, rect_);
641
+ op2 = op;
642
+ do
643
+ {
644
+ uint32_t edgeSet2 = GetEdgesForPt(op2->pt, rect_);
645
+ if (edgeSet2 && !op2->edge)
646
+ {
647
+ uint32_t combinedSet = (edgeSet1 & edgeSet2);
648
+ for (int j = 0; j < 4; ++j)
649
+ {
650
+ if (combinedSet & (1 << j))
651
+ {
652
+ if (IsHeadingClockwise(op2->prev->pt, op2->pt, j))
653
+ AddToEdge(edges_[j * 2], op2);
654
+ else
655
+ AddToEdge(edges_[j * 2 + 1], op2);
656
+ }
657
+ }
658
+ }
659
+ edgeSet1 = edgeSet2;
660
+ op2 = op2->next;
661
+ } while (op2 != op);
662
+ }
663
+ }
664
+
665
+ void RectClip64::TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw)
666
+ {
667
+ if (ccw.empty()) return;
668
+ bool isHorz = ((idx == 1) || (idx == 3));
669
+ bool cwIsTowardLarger = ((idx == 1) || (idx == 2));
670
+ size_t i = 0, j = 0;
671
+ OutPt2* p1, * p2, * p1a, * p2a, * op, * op2;
672
+
673
+ while (i < cw.size())
674
+ {
675
+ p1 = cw[i];
676
+ if (!p1 || p1->next == p1->prev)
677
+ {
678
+ cw[i++] = nullptr;
679
+ j = 0;
680
+ continue;
681
+ }
682
+
683
+ size_t jLim = ccw.size();
684
+ while (j < jLim &&
685
+ (!ccw[j] || ccw[j]->next == ccw[j]->prev)) ++j;
686
+
687
+ if (j == jLim)
688
+ {
689
+ ++i;
690
+ j = 0;
691
+ continue;
692
+ }
693
+
694
+ if (cwIsTowardLarger)
695
+ {
696
+ // p1 >>>> p1a;
697
+ // p2 <<<< p2a;
698
+ p1 = cw[i]->prev;
699
+ p1a = cw[i];
700
+ p2 = ccw[j];
701
+ p2a = ccw[j]->prev;
702
+ }
703
+ else
704
+ {
705
+ // p1 <<<< p1a;
706
+ // p2 >>>> p2a;
707
+ p1 = cw[i];
708
+ p1a = cw[i]->prev;
709
+ p2 = ccw[j]->prev;
710
+ p2a = ccw[j];
711
+ }
712
+
713
+ if ((isHorz && !HasHorzOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt)) ||
714
+ (!isHorz && !HasVertOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt)))
715
+ {
716
+ ++j;
717
+ continue;
718
+ }
719
+
720
+ // to get here we're either splitting or rejoining
721
+ bool isRejoining = cw[i]->owner_idx != ccw[j]->owner_idx;
722
+
723
+ if (isRejoining)
724
+ {
725
+ results_[p2->owner_idx] = nullptr;
726
+ SetNewOwner(p2, p1->owner_idx);
727
+ }
728
+
729
+ // do the split or re-join
730
+ if (cwIsTowardLarger)
731
+ {
732
+ // p1 >> | >> p1a;
733
+ // p2 << | << p2a;
734
+ p1->next = p2;
735
+ p2->prev = p1;
736
+ p1a->prev = p2a;
737
+ p2a->next = p1a;
738
+ }
739
+ else
740
+ {
741
+ // p1 << | << p1a;
742
+ // p2 >> | >> p2a;
743
+ p1->prev = p2;
744
+ p2->next = p1;
745
+ p1a->next = p2a;
746
+ p2a->prev = p1a;
747
+ }
748
+
749
+ if (!isRejoining)
750
+ {
751
+ size_t new_idx = results_.size();
752
+ results_.emplace_back(p1a);
753
+ SetNewOwner(p1a, new_idx);
754
+ }
755
+
756
+ if (cwIsTowardLarger)
757
+ {
758
+ op = p2;
759
+ op2 = p1a;
760
+ }
761
+ else
762
+ {
763
+ op = p1;
764
+ op2 = p2a;
765
+ }
766
+ results_[op->owner_idx] = op;
767
+ results_[op2->owner_idx] = op2;
768
+
769
+ // and now lots of work to get ready for the next loop
770
+
771
+ bool opIsLarger, op2IsLarger;
772
+ if (isHorz) // X
773
+ {
774
+ opIsLarger = op->pt.x > op->prev->pt.x;
775
+ op2IsLarger = op2->pt.x > op2->prev->pt.x;
776
+ }
777
+ else // Y
778
+ {
779
+ opIsLarger = op->pt.y > op->prev->pt.y;
780
+ op2IsLarger = op2->pt.y > op2->prev->pt.y;
781
+ }
782
+
783
+ if ((op->next == op->prev) ||
784
+ (op->pt == op->prev->pt))
785
+ {
786
+ if (op2IsLarger == cwIsTowardLarger)
787
+ {
788
+ cw[i] = op2;
789
+ ccw[j++] = nullptr;
790
+ }
791
+ else
792
+ {
793
+ ccw[j] = op2;
794
+ cw[i++] = nullptr;
795
+ }
796
+ }
797
+ else if ((op2->next == op2->prev) ||
798
+ (op2->pt == op2->prev->pt))
799
+ {
800
+ if (opIsLarger == cwIsTowardLarger)
801
+ {
802
+ cw[i] = op;
803
+ ccw[j++] = nullptr;
804
+ }
805
+ else
806
+ {
807
+ ccw[j] = op;
808
+ cw[i++] = nullptr;
809
+ }
810
+ }
811
+ else if (opIsLarger == op2IsLarger)
812
+ {
813
+ if (opIsLarger == cwIsTowardLarger)
814
+ {
815
+ cw[i] = op;
816
+ UncoupleEdge(op2);
817
+ AddToEdge(cw, op2);
818
+ ccw[j++] = nullptr;
819
+ }
820
+ else
821
+ {
822
+ cw[i++] = nullptr;
823
+ ccw[j] = op2;
824
+ UncoupleEdge(op);
825
+ AddToEdge(ccw, op);
826
+ j = 0;
827
+ }
828
+ }
829
+ else
830
+ {
831
+ if (opIsLarger == cwIsTowardLarger)
832
+ cw[i] = op;
833
+ else
834
+ ccw[j] = op;
835
+ if (op2IsLarger == cwIsTowardLarger)
836
+ cw[i] = op2;
837
+ else
838
+ ccw[j] = op2;
839
+ }
840
+ }
841
+ }
842
+
843
+ Path64 RectClip64::GetPath(OutPt2*& op)
844
+ {
845
+ if (!op || op->next == op->prev) return Path64();
846
+
847
+ OutPt2* op2 = op->next;
848
+ while (op2 && op2 != op)
849
+ {
850
+ if (IsCollinear(op2->prev->pt,
851
+ op2->pt, op2->next->pt))
852
+ {
853
+ op = op2->prev;
854
+ op2 = UnlinkOp(op2);
855
+ }
856
+ else
857
+ op2 = op2->next;
858
+ }
859
+ op = op2; // needed for op cleanup
860
+ if (!op2) return Path64();
861
+
862
+ Path64 result;
863
+ result.emplace_back(op->pt);
864
+ op2 = op->next;
865
+ while (op2 != op)
866
+ {
867
+ result.emplace_back(op2->pt);
868
+ op2 = op2->next;
869
+ }
870
+ return result;
871
+ }
872
+
873
+ Paths64 RectClip64::Execute(const Paths64& paths)
874
+ {
875
+ Paths64 result;
876
+ if (rect_.IsEmpty()) return result;
877
+
878
+ for (const Path64& path : paths)
879
+ {
880
+ if (path.size() < 3) continue;
881
+ path_bounds_ = GetBounds(path);
882
+ if (!rect_.Intersects(path_bounds_))
883
+ continue; // the path must be completely outside rect_
884
+ else if (rect_.Contains(path_bounds_))
885
+ {
886
+ // the path must be completely inside rect_
887
+ result.emplace_back(path);
888
+ continue;
889
+ }
890
+
891
+ ExecuteInternal(path);
892
+ CheckEdges();
893
+ for (size_t i = 0; i < 4; ++i)
894
+ TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]);
895
+
896
+ for (OutPt2*& op : results_)
897
+ {
898
+ Path64 tmp = GetPath(op);
899
+ if (!tmp.empty())
900
+ result.emplace_back(std::move(tmp));
901
+ }
902
+
903
+ //clean up after every loop
904
+ op_container_ = std::deque<OutPt2>();
905
+ results_.clear();
906
+ for (OutPt2List &edge : edges_) edge.clear();
907
+ start_locs_.clear();
908
+ }
909
+ return result;
910
+ }
911
+
912
+ //------------------------------------------------------------------------------
913
+ // RectClipLines64
914
+ //------------------------------------------------------------------------------
915
+
916
+ Paths64 RectClipLines64::Execute(const Paths64& paths)
917
+ {
918
+ Paths64 result;
919
+ if (rect_.IsEmpty()) return result;
920
+
921
+ for (const auto& path : paths)
922
+ {
923
+ Rect64 pathrec = GetBounds(path);
924
+ if (!rect_.Intersects(pathrec)) continue;
925
+
926
+ ExecuteInternal(path);
927
+
928
+ for (OutPt2*& op : results_)
929
+ {
930
+ Path64 tmp = GetPath(op);
931
+ if (!tmp.empty())
932
+ result.emplace_back(std::move(tmp));
933
+ }
934
+ results_.clear();
935
+
936
+ op_container_ = std::deque<OutPt2>();
937
+ start_locs_.clear();
938
+ }
939
+ return result;
940
+ }
941
+
942
+ void RectClipLines64::ExecuteInternal(const Path64& path)
943
+ {
944
+ if (rect_.IsEmpty() || path.size() < 2) return;
945
+
946
+ results_.clear();
947
+ op_container_ = std::deque<OutPt2>();
948
+ start_locs_.clear();
949
+
950
+ size_t i = 1, highI = path.size() - 1;
951
+
952
+ Location prev = Location::Inside, loc;
953
+ Location crossing_loc;
954
+ if (!GetLocation(rect_, path[0], loc))
955
+ {
956
+ while (i <= highI && !GetLocation(rect_, path[i], prev)) ++i;
957
+ if (i > highI)
958
+ {
959
+ // all of path must be inside fRect
960
+ for (const auto& pt : path) Add(pt);
961
+ return;
962
+ }
963
+ if (prev == Location::Inside) loc = Location::Inside;
964
+ i = 1;
965
+ }
966
+ if (loc == Location::Inside) Add(path[0]);
967
+
968
+ ///////////////////////////////////////////////////
969
+ while (i <= highI)
970
+ {
971
+ prev = loc;
972
+ GetNextLocation(path, loc, i, highI);
973
+ if (i > highI) break;
974
+ Point64 ip, ip2;
975
+ Point64 prev_pt = path[static_cast<size_t>(i - 1)];
976
+
977
+ crossing_loc = loc;
978
+ if (!GetIntersection(rect_as_path_,
979
+ path[i], prev_pt, crossing_loc, ip))
980
+ {
981
+ // ie remaining outside
982
+ ++i;
983
+ continue;
984
+ }
985
+
986
+ ////////////////////////////////////////////////////
987
+ // we must be crossing the rect boundary to get here
988
+ ////////////////////////////////////////////////////
989
+
990
+ if (loc == Location::Inside) // path must be entering rect
991
+ {
992
+ Add(ip, true);
993
+ }
994
+ else if (prev != Location::Inside)
995
+ {
996
+ // passing right through rect. 'ip' here will be the second
997
+ // intersect pt but we'll also need the first intersect pt (ip2)
998
+ crossing_loc = prev;
999
+ GetIntersection(rect_as_path_,
1000
+ prev_pt, path[i], crossing_loc, ip2);
1001
+ Add(ip2, true);
1002
+ Add(ip);
1003
+ }
1004
+ else // path must be exiting rect
1005
+ {
1006
+ Add(ip);
1007
+ }
1008
+ } //while i <= highI
1009
+ ///////////////////////////////////////////////////
1010
+ }
1011
+
1012
+ Path64 RectClipLines64::GetPath(OutPt2*& op)
1013
+ {
1014
+ Path64 result;
1015
+ if (!op || op == op->next) return result;
1016
+ op = op->next; // starting at path beginning
1017
+ result.emplace_back(op->pt);
1018
+ OutPt2 *op2 = op->next;
1019
+ while (op2 != op)
1020
+ {
1021
+ result.emplace_back(op2->pt);
1022
+ op2 = op2->next;
1023
+ }
1024
+ return result;
1025
+ }
1026
+
1027
+ } // namespace