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,796 @@
1
+ /*******************************************************************************
2
+ * Author : Angus Johnson *
3
+ * Date : 5 March 2025 *
4
+ * Website : https://www.angusj.com *
5
+ * Copyright : Angus Johnson 2010-2025 *
6
+ * Purpose : This module provides a simple interface to the Clipper Library *
7
+ * License : https://www.boost.org/LICENSE_1_0.txt *
8
+ *******************************************************************************/
9
+
10
+ #ifndef CLIPPER_H
11
+ #define CLIPPER_H
12
+
13
+ #include "clipper2/clipper.core.h"
14
+ #include "clipper2/clipper.engine.h"
15
+ #include "clipper2/clipper.offset.h"
16
+ #include "clipper2/clipper.rectclip.h"
17
+ #include "clipper2/clipper.minkowski.h"
18
+ #include "clipper2/clipper.triangulation.h"
19
+ #include <type_traits>
20
+
21
+ namespace Clipper2Lib {
22
+
23
+ inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
24
+ const Paths64& subjects, const Paths64& clips)
25
+ {
26
+ Paths64 result;
27
+ Clipper64 clipper;
28
+ clipper.AddSubject(subjects);
29
+ clipper.AddClip(clips);
30
+ clipper.Execute(cliptype, fillrule, result);
31
+ return result;
32
+ }
33
+
34
+ inline void BooleanOp(ClipType cliptype, FillRule fillrule,
35
+ const Paths64& subjects, const Paths64& clips, PolyTree64& solution)
36
+ {
37
+ Paths64 sol_open;
38
+ Clipper64 clipper;
39
+ clipper.AddSubject(subjects);
40
+ clipper.AddClip(clips);
41
+ clipper.Execute(cliptype, fillrule, solution, sol_open);
42
+ }
43
+
44
+ inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule,
45
+ const PathsD& subjects, const PathsD& clips, int precision = 2)
46
+ {
47
+ int error_code = 0;
48
+ CheckPrecisionRange(precision, error_code);
49
+ PathsD result;
50
+ if (error_code) return result;
51
+ ClipperD clipper(precision);
52
+ clipper.AddSubject(subjects);
53
+ clipper.AddClip(clips);
54
+ clipper.Execute(cliptype, fillrule, result);
55
+ return result;
56
+ }
57
+
58
+ inline void BooleanOp(ClipType cliptype, FillRule fillrule,
59
+ const PathsD& subjects, const PathsD& clips,
60
+ PolyTreeD& polytree, int precision = 2)
61
+ {
62
+ polytree.Clear();
63
+ int error_code = 0;
64
+ CheckPrecisionRange(precision, error_code);
65
+ if (error_code) return;
66
+ ClipperD clipper(precision);
67
+ clipper.AddSubject(subjects);
68
+ clipper.AddClip(clips);
69
+ clipper.Execute(cliptype, fillrule, polytree);
70
+ }
71
+
72
+ inline Paths64 Intersect(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
73
+ {
74
+ return BooleanOp(ClipType::Intersection, fillrule, subjects, clips);
75
+ }
76
+
77
+ inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
78
+ {
79
+ return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec);
80
+ }
81
+
82
+ inline Paths64 Union(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
83
+ {
84
+ return BooleanOp(ClipType::Union, fillrule, subjects, clips);
85
+ }
86
+
87
+ inline PathsD Union(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
88
+ {
89
+ return BooleanOp(ClipType::Union, fillrule, subjects, clips, decimal_prec);
90
+ }
91
+
92
+ inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
93
+ {
94
+ Paths64 result;
95
+ Clipper64 clipper;
96
+ clipper.AddSubject(subjects);
97
+ clipper.Execute(ClipType::Union, fillrule, result);
98
+ return result;
99
+ }
100
+
101
+ inline PathsD Union(const PathsD& subjects, FillRule fillrule, int precision = 2)
102
+ {
103
+ PathsD result;
104
+ int error_code = 0;
105
+ CheckPrecisionRange(precision, error_code);
106
+ if (error_code) return result;
107
+ ClipperD clipper(precision);
108
+ clipper.AddSubject(subjects);
109
+ clipper.Execute(ClipType::Union, fillrule, result);
110
+ return result;
111
+ }
112
+
113
+ inline Paths64 Difference(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
114
+ {
115
+ return BooleanOp(ClipType::Difference, fillrule, subjects, clips);
116
+ }
117
+
118
+ inline PathsD Difference(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
119
+ {
120
+ return BooleanOp(ClipType::Difference, fillrule, subjects, clips, decimal_prec);
121
+ }
122
+
123
+ inline Paths64 Xor(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
124
+ {
125
+ return BooleanOp(ClipType::Xor, fillrule, subjects, clips);
126
+ }
127
+
128
+ inline PathsD Xor(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
129
+ {
130
+ return BooleanOp(ClipType::Xor, fillrule, subjects, clips, decimal_prec);
131
+ }
132
+
133
+ inline Paths64 InflatePaths(const Paths64& paths, double delta,
134
+ JoinType jt, EndType et, double miter_limit = 2.0,
135
+ double arc_tolerance = 0.0)
136
+ {
137
+ if (delta==0.0) return paths;
138
+ ClipperOffset clip_offset(miter_limit, arc_tolerance);
139
+ clip_offset.AddPaths(paths, jt, et);
140
+ Paths64 solution;
141
+ clip_offset.Execute(delta, solution);
142
+ return solution;
143
+ }
144
+
145
+ inline PathsD InflatePaths(const PathsD& paths, double delta,
146
+ JoinType jt, EndType et, double miter_limit = 2.0,
147
+ int precision = 2, double arc_tolerance = 0.0)
148
+ {
149
+ int error_code = 0;
150
+ CheckPrecisionRange(precision, error_code);
151
+ if (delta==0.0) return paths;
152
+ if (error_code) return PathsD();
153
+ const double scale = std::pow(10, precision);
154
+ ClipperOffset clip_offset(miter_limit, arc_tolerance * scale);
155
+ clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et);
156
+ if (error_code) return PathsD();
157
+ Paths64 solution;
158
+ clip_offset.Execute(delta * scale, solution);
159
+ return ScalePaths<double, int64_t>(solution, 1 / scale, error_code);
160
+ }
161
+
162
+ template <typename T>
163
+ inline Path<T> TranslatePath(const Path<T>& path, T dx, T dy)
164
+ {
165
+ Path<T> result;
166
+ result.reserve(path.size());
167
+ std::transform(path.begin(), path.end(), back_inserter(result),
168
+ [dx, dy](const auto& pt) { return Point<T>(pt.x + dx, pt.y +dy); });
169
+ return result;
170
+ }
171
+
172
+ inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
173
+ {
174
+ return TranslatePath<int64_t>(path, dx, dy);
175
+ }
176
+
177
+ inline PathD TranslatePath(const PathD& path, double dx, double dy)
178
+ {
179
+ return TranslatePath<double>(path, dx, dy);
180
+ }
181
+
182
+ template <typename T>
183
+ inline Paths<T> TranslatePaths(const Paths<T>& paths, T dx, T dy)
184
+ {
185
+ Paths<T> result;
186
+ result.reserve(paths.size());
187
+ std::transform(paths.begin(), paths.end(), back_inserter(result),
188
+ [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
189
+ return result;
190
+ }
191
+
192
+ inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
193
+ {
194
+ return TranslatePaths<int64_t>(paths, dx, dy);
195
+ }
196
+
197
+ inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
198
+ {
199
+ return TranslatePaths<double>(paths, dx, dy);
200
+ }
201
+
202
+ inline Paths64 RectClip(const Rect64& rect, const Paths64& paths)
203
+ {
204
+ if (rect.IsEmpty() || paths.empty()) return Paths64();
205
+ RectClip64 rc(rect);
206
+ return rc.Execute(paths);
207
+ }
208
+
209
+ inline Paths64 RectClip(const Rect64& rect, const Path64& path)
210
+ {
211
+ if (rect.IsEmpty() || path.empty()) return Paths64();
212
+ RectClip64 rc(rect);
213
+ return rc.Execute(Paths64{ path });
214
+ }
215
+
216
+ inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2)
217
+ {
218
+ if (rect.IsEmpty() || paths.empty()) return PathsD();
219
+ int error_code = 0;
220
+ CheckPrecisionRange(precision, error_code);
221
+ if (error_code) return PathsD();
222
+ const double scale = std::pow(10, precision);
223
+ Rect64 r = ScaleRect<int64_t, double>(rect, scale);
224
+ RectClip64 rc(r);
225
+ Paths64 pp = ScalePaths<int64_t, double>(paths, scale, error_code);
226
+ if (error_code) return PathsD(); // ie: error_code result is lost
227
+ return ScalePaths<double, int64_t>(
228
+ rc.Execute(pp), 1 / scale, error_code);
229
+ }
230
+
231
+ inline PathsD RectClip(const RectD& rect, const PathD& path, int precision = 2)
232
+ {
233
+ return RectClip(rect, PathsD{ path }, precision);
234
+ }
235
+
236
+ inline Paths64 RectClipLines(const Rect64& rect, const Paths64& lines)
237
+ {
238
+ if (rect.IsEmpty() || lines.empty()) return Paths64();
239
+ RectClipLines64 rcl(rect);
240
+ return rcl.Execute(lines);
241
+ }
242
+
243
+ inline Paths64 RectClipLines(const Rect64& rect, const Path64& line)
244
+ {
245
+ return RectClipLines(rect, Paths64{ line });
246
+ }
247
+
248
+ inline PathsD RectClipLines(const RectD& rect, const PathsD& lines, int precision = 2)
249
+ {
250
+ if (rect.IsEmpty() || lines.empty()) return PathsD();
251
+ int error_code = 0;
252
+ CheckPrecisionRange(precision, error_code);
253
+ if (error_code) return PathsD();
254
+ const double scale = std::pow(10, precision);
255
+ Rect64 r = ScaleRect<int64_t, double>(rect, scale);
256
+ RectClipLines64 rcl(r);
257
+ Paths64 p = ScalePaths<int64_t, double>(lines, scale, error_code);
258
+ if (error_code) return PathsD();
259
+ p = rcl.Execute(p);
260
+ return ScalePaths<double, int64_t>(p, 1 / scale, error_code);
261
+ }
262
+
263
+ inline PathsD RectClipLines(const RectD& rect, const PathD& line, int precision = 2)
264
+ {
265
+ return RectClipLines(rect, PathsD{ line }, precision);
266
+ }
267
+
268
+ namespace details
269
+ {
270
+
271
+ inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths)
272
+ {
273
+ paths.emplace_back(polypath.Polygon());
274
+ for (const auto& child : polypath)
275
+ PolyPathToPaths64(*child, paths);
276
+ }
277
+
278
+ inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths)
279
+ {
280
+ paths.emplace_back(polypath.Polygon());
281
+ for (const auto& child : polypath)
282
+ PolyPathToPathsD(*child, paths);
283
+ }
284
+
285
+ inline bool PolyPath64ContainsChildren(const PolyPath64& pp)
286
+ {
287
+ for (const auto& child : pp)
288
+ {
289
+ // return false if this child isn't fully contained by its parent
290
+
291
+ // checking for a single vertex outside is a bit too crude since
292
+ // it doesn't account for rounding errors. It's better to check
293
+ // for consecutive vertices found outside the parent's polygon.
294
+
295
+ int outsideCnt = 0;
296
+ for (const Point64& pt : child->Polygon())
297
+ {
298
+ PointInPolygonResult result = PointInPolygon(pt, pp.Polygon());
299
+ if (result == PointInPolygonResult::IsInside) --outsideCnt;
300
+ else if (result == PointInPolygonResult::IsOutside) ++outsideCnt;
301
+ if (outsideCnt > 1) return false;
302
+ else if (outsideCnt < -1) break;
303
+ }
304
+
305
+ // now check any nested children too
306
+ if (child->Count() > 0 && !PolyPath64ContainsChildren(*child))
307
+ return false;
308
+ }
309
+ return true;
310
+ }
311
+
312
+ static void OutlinePolyPath(std::ostream& os,
313
+ size_t idx, bool isHole, size_t count, const std::string& preamble)
314
+ {
315
+ std::string plural = (count == 1) ? "." : "s.";
316
+ if (isHole)
317
+ os << preamble << "+- Hole (" << idx << ") contains " << count <<
318
+ " nested polygon" << plural << std::endl;
319
+ else
320
+ os << preamble << "+- Polygon (" << idx << ") contains " << count <<
321
+ " hole" << plural << std::endl;
322
+ }
323
+
324
+ static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp,
325
+ size_t idx, std::string preamble)
326
+ {
327
+ OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble);
328
+ for (size_t i = 0; i < pp.Count(); ++i)
329
+ if (pp.Child(i)->Count())
330
+ details::OutlinePolyPath64(os, *pp.Child(i), i, preamble + " ");
331
+ }
332
+
333
+ static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp,
334
+ size_t idx, std::string preamble)
335
+ {
336
+ OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble);
337
+ for (size_t i = 0; i < pp.Count(); ++i)
338
+ if (pp.Child(i)->Count())
339
+ details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + " ");
340
+ }
341
+
342
+ template<typename T, typename U>
343
+ inline constexpr void MakePathGeneric(const T an_array,
344
+ size_t array_size, std::vector<U>& result)
345
+ {
346
+ result.reserve(array_size / 2);
347
+ for (size_t i = 0; i < array_size; i +=2)
348
+ #ifdef USINGZ
349
+ result.emplace_back( an_array[i], an_array[i + 1], 0 );
350
+ #else
351
+ result.emplace_back( an_array[i], an_array[i + 1] );
352
+ #endif
353
+ }
354
+
355
+ inline size_t GetNext(size_t current, size_t high,
356
+ const std::vector<bool>& flags)
357
+ {
358
+ ++current;
359
+ while (current <= high && flags[current]) ++current;
360
+ if (current <= high) return current;
361
+ current = 0;
362
+ while (flags[current]) ++current;
363
+ return current;
364
+ }
365
+
366
+ inline size_t GetPrior(size_t current, size_t high,
367
+ const std::vector<bool>& flags)
368
+ {
369
+ if (current == 0) current = high;
370
+ else --current;
371
+ while (current > 0 && flags[current]) --current;
372
+ if (!flags[current]) return current;
373
+ current = high;
374
+ while (flags[current]) --current;
375
+ return current;
376
+ }
377
+
378
+ } // end details namespace
379
+
380
+ inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
381
+ {
382
+ std::string plural = (pp.Count() == 1) ? " polygon." : " polygons.";
383
+ os << std::endl << "Polytree with " << pp.Count() << plural << std::endl;
384
+ for (size_t i = 0; i < pp.Count(); ++i)
385
+ if (pp.Child(i)->Count())
386
+ details::OutlinePolyPath64(os, *pp.Child(i), i, " ");
387
+ os << std::endl << std::endl;
388
+ return os;
389
+ }
390
+
391
+ inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp)
392
+ {
393
+ std::string plural = (pp.Count() == 1) ? " polygon." : " polygons.";
394
+ os << std::endl << "Polytree with " << pp.Count() << plural << std::endl;
395
+ for (size_t i = 0; i < pp.Count(); ++i)
396
+ if (pp.Child(i)->Count())
397
+ details::OutlinePolyPathD(os, *pp.Child(i), i, " ");
398
+ os << std::endl << std::endl;
399
+ if (!pp.Level()) os << std::endl;
400
+ return os;
401
+ }
402
+
403
+ inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree)
404
+ {
405
+ Paths64 result;
406
+ for (const auto& child : polytree)
407
+ details::PolyPathToPaths64(*child, result);
408
+ return result;
409
+ }
410
+
411
+ inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree)
412
+ {
413
+ PathsD result;
414
+ for (const auto& child : polytree)
415
+ details::PolyPathToPathsD(*child, result);
416
+ return result;
417
+ }
418
+
419
+ inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree)
420
+ {
421
+ for (const auto& child : polytree)
422
+ if (child->Count() > 0 &&
423
+ !details::PolyPath64ContainsChildren(*child))
424
+ return false;
425
+ return true;
426
+ }
427
+
428
+ template<typename T,
429
+ typename std::enable_if<
430
+ std::is_integral<T>::value &&
431
+ !std::is_same<char, T>::value, bool
432
+ >::type = true>
433
+ inline Path64 MakePath(const std::vector<T>& list)
434
+ {
435
+ const auto size = list.size() - list.size() % 2;
436
+ if (list.size() != size)
437
+ DoError(non_pair_error_i); // non-fatal without exception handling
438
+ Path64 result;
439
+ details::MakePathGeneric(list, size, result);
440
+ return result;
441
+ }
442
+
443
+ template<typename T, std::size_t N,
444
+ typename std::enable_if<
445
+ std::is_integral<T>::value &&
446
+ !std::is_same<char, T>::value, bool
447
+ >::type = true>
448
+ inline Path64 MakePath(const T(&list)[N])
449
+ {
450
+ // Make the compiler error on unpaired value (i.e. no runtime effects).
451
+ static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
452
+ Path64 result;
453
+ details::MakePathGeneric(list, N, result);
454
+ return result;
455
+ }
456
+
457
+ template<typename T,
458
+ typename std::enable_if<
459
+ std::is_arithmetic<T>::value &&
460
+ !std::is_same<char, T>::value, bool
461
+ >::type = true>
462
+ inline PathD MakePathD(const std::vector<T>& list)
463
+ {
464
+ const auto size = list.size() - list.size() % 2;
465
+ if (list.size() != size)
466
+ DoError(non_pair_error_i); // non-fatal without exception handling
467
+ PathD result;
468
+ details::MakePathGeneric(list, size, result);
469
+ return result;
470
+ }
471
+
472
+ template<typename T, std::size_t N,
473
+ typename std::enable_if<
474
+ std::is_arithmetic<T>::value &&
475
+ !std::is_same<char, T>::value, bool
476
+ >::type = true>
477
+ inline PathD MakePathD(const T(&list)[N])
478
+ {
479
+ // Make the compiler error on unpaired value (i.e. no runtime effects).
480
+ static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
481
+ PathD result;
482
+ details::MakePathGeneric(list, N, result);
483
+ return result;
484
+ }
485
+
486
+ #ifdef USINGZ
487
+ template<typename T2, std::size_t N>
488
+ inline Path64 MakePathZ(const T2(&list)[N])
489
+ {
490
+ static_assert(N % 3 == 0 && std::numeric_limits<T2>::is_integer,
491
+ "MakePathZ requires integer values in multiples of 3");
492
+ std::size_t size = N / 3;
493
+ Path64 result(size);
494
+ for (size_t i = 0; i < size; ++i)
495
+ result[i] = Point64(list[i * 3],
496
+ list[i * 3 + 1], list[i * 3 + 2]);
497
+ return result;
498
+ }
499
+
500
+ template<typename T2, std::size_t N>
501
+ inline PathD MakePathZD(const T2(&list)[N])
502
+ {
503
+ static_assert(N % 3 == 0,
504
+ "MakePathZD requires values in multiples of 3");
505
+ std::size_t size = N / 3;
506
+ PathD result(size);
507
+ if constexpr (std::numeric_limits<T2>::is_integer)
508
+ for (size_t i = 0; i < size; ++i)
509
+ result[i] = PointD(list[i * 3],
510
+ list[i * 3 + 1], list[i * 3 + 2]);
511
+ else
512
+ for (size_t i = 0; i < size; ++i)
513
+ result[i] = PointD(list[i * 3], list[i * 3 + 1],
514
+ static_cast<int64_t>(list[i * 3 + 2]));
515
+ return result;
516
+ }
517
+ #endif
518
+
519
+ inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false)
520
+ {
521
+ size_t len = p.size();
522
+ if (len < 3)
523
+ {
524
+ if (!is_open_path || len < 2 || p[0] == p[1]) return Path64();
525
+ else return p;
526
+ }
527
+
528
+ Path64 dst;
529
+ dst.reserve(len);
530
+ Path64::const_iterator srcIt = p.cbegin(), prevIt, stop = p.cend() - 1;
531
+
532
+ if (!is_open_path)
533
+ {
534
+ while (srcIt != stop && IsCollinear(*stop, *srcIt, *(srcIt + 1)))
535
+ ++srcIt;
536
+ while (srcIt != stop && IsCollinear(*(stop - 1), *stop, *srcIt))
537
+ --stop;
538
+ if (srcIt == stop) return Path64();
539
+ }
540
+
541
+ prevIt = srcIt++;
542
+ dst.emplace_back(*prevIt);
543
+ for (; srcIt != stop; ++srcIt)
544
+ {
545
+ if (!IsCollinear(*prevIt, *srcIt, *(srcIt + 1)))
546
+ {
547
+ prevIt = srcIt;
548
+ dst.emplace_back(*prevIt);
549
+ }
550
+ }
551
+
552
+ if (is_open_path)
553
+ dst.emplace_back(*srcIt);
554
+ else if (!IsCollinear(*prevIt, *stop, dst[0]))
555
+ dst.emplace_back(*stop);
556
+ else
557
+ {
558
+ while (dst.size() > 2 &&
559
+ IsCollinear(dst[dst.size() - 1], dst[dst.size() - 2], dst[0]))
560
+ dst.pop_back();
561
+ if (dst.size() < 3) return Path64();
562
+ }
563
+ return dst;
564
+ }
565
+
566
+ inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false)
567
+ {
568
+ int error_code = 0;
569
+ CheckPrecisionRange(precision, error_code);
570
+ if (error_code) return PathD();
571
+ const double scale = std::pow(10, precision);
572
+ Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
573
+ if (error_code) return PathD();
574
+ p = TrimCollinear(p, is_open_path);
575
+ return ScalePath<double, int64_t>(p, 1/scale, error_code);
576
+ }
577
+
578
+ template <typename T>
579
+ inline double Distance(const Point<T> pt1, const Point<T> pt2)
580
+ {
581
+ return std::sqrt(DistanceSqr(pt1, pt2));
582
+ }
583
+
584
+ template <typename T>
585
+ inline double Length(const Path<T>& path, bool is_closed_path = false)
586
+ {
587
+ double result = 0.0;
588
+ if (path.size() < 2) return result;
589
+ auto it = path.cbegin(), stop = path.end() - 1;
590
+ for (; it != stop; ++it)
591
+ result += Distance(*it, *(it + 1));
592
+ if (is_closed_path)
593
+ result += Distance(*stop, *path.cbegin());
594
+ return result;
595
+ }
596
+
597
+
598
+ template <typename T>
599
+ inline bool NearCollinear(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3, double sin_sqrd_min_angle_rads)
600
+ {
601
+ double cp = std::abs(CrossProduct(pt1, pt2, pt3));
602
+ return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads;
603
+ }
604
+
605
+ template <typename T>
606
+ inline Path<T> Ellipse(const Rect<T>& rect, size_t steps = 0)
607
+ {
608
+ return Ellipse(rect.MidPoint(),
609
+ static_cast<double>(rect.Width()) *0.5,
610
+ static_cast<double>(rect.Height()) * 0.5, steps);
611
+ }
612
+
613
+ template <typename T>
614
+ inline Path<T> Ellipse(const Point<T>& center,
615
+ double radiusX, double radiusY = 0, size_t steps = 0)
616
+ {
617
+ if (radiusX <= 0) return Path<T>();
618
+ if (radiusY <= 0) radiusY = radiusX;
619
+ if (steps <= 2)
620
+ steps = static_cast<size_t>(PI * sqrt((radiusX + radiusY) / 2));
621
+
622
+ double si = std::sin(2 * PI / steps);
623
+ double co = std::cos(2 * PI / steps);
624
+ double dx = co, dy = si;
625
+ Path<T> result;
626
+ result.reserve(steps);
627
+ result.emplace_back(center.x + radiusX, static_cast<double>(center.y));
628
+ for (size_t i = 1; i < steps; ++i)
629
+ {
630
+ result.emplace_back(center.x + radiusX * dx, center.y + radiusY * dy);
631
+ double x = dx * co - dy * si;
632
+ dy = dy * co + dx * si;
633
+ dx = x;
634
+ }
635
+ return result;
636
+ }
637
+
638
+ template <typename T>
639
+ inline Path<T> SimplifyPath(const Path<T> &path,
640
+ double epsilon, bool isClosedPath = true)
641
+ {
642
+ const size_t len = path.size(), high = len -1;
643
+ const double epsSqr = Sqr(epsilon);
644
+ if (len < 4) return Path<T>(path);
645
+
646
+ std::vector<bool> flags(len);
647
+ std::vector<double> distSqr(len);
648
+ size_t prior = high, curr = 0, start, next, prior2;
649
+ if (isClosedPath)
650
+ {
651
+ distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
652
+ distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
653
+ }
654
+ else
655
+ {
656
+ distSqr[0] = MAX_DBL;
657
+ distSqr[high] = MAX_DBL;
658
+ }
659
+ for (size_t i = 1; i < high; ++i)
660
+ distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
661
+
662
+ for (;;)
663
+ {
664
+ if (distSqr[curr] > epsSqr)
665
+ {
666
+ start = curr;
667
+ do
668
+ {
669
+ curr = details::GetNext(curr, high, flags);
670
+ } while (curr != start && distSqr[curr] > epsSqr);
671
+ if (curr == start) break;
672
+ }
673
+
674
+ prior = details::GetPrior(curr, high, flags);
675
+ next = details::GetNext(curr, high, flags);
676
+ if (next == prior) break;
677
+
678
+ // flag for removal the smaller of adjacent 'distances'
679
+ if (distSqr[next] < distSqr[curr])
680
+ {
681
+ prior2 = prior;
682
+ prior = curr;
683
+ curr = next;
684
+ next = details::GetNext(next, high, flags);
685
+ }
686
+ else
687
+ prior2 = details::GetPrior(prior, high, flags);
688
+
689
+ flags[curr] = true;
690
+ curr = next;
691
+ next = details::GetNext(next, high, flags);
692
+
693
+ if (isClosedPath || ((curr != high) && (curr != 0)))
694
+ distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
695
+ if (isClosedPath || ((prior != 0) && (prior != high)))
696
+ distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]);
697
+ }
698
+ Path<T> result;
699
+ result.reserve(len);
700
+ for (typename Path<T>::size_type i = 0; i < len; ++i)
701
+ if (!flags[i]) result.emplace_back(path[i]);
702
+ return result;
703
+ }
704
+
705
+ template <typename T>
706
+ inline Paths<T> SimplifyPaths(const Paths<T> &paths,
707
+ double epsilon, bool isClosedPath = true)
708
+ {
709
+ Paths<T> result;
710
+ result.reserve(paths.size());
711
+ for (const auto& path : paths)
712
+ result.emplace_back(std::move(SimplifyPath(path, epsilon, isClosedPath)));
713
+ return result;
714
+ }
715
+
716
+
717
+ template <typename T>
718
+ inline bool Path2ContainsPath1(const Path<T>& path1, const Path<T>& path2)
719
+ {
720
+ // precondition: paths must not intersect, except for
721
+ // transient (and presumed 'micro') path intersections
722
+ PointInPolygonResult pip = PointInPolygonResult::IsOn;
723
+ for (const Point<T>& pt : path1)
724
+ {
725
+ switch (PointInPolygon(pt, path2))
726
+ {
727
+ case PointInPolygonResult::IsOutside:
728
+ if (pip == PointInPolygonResult::IsOutside) return false;
729
+ pip = PointInPolygonResult::IsOutside;
730
+ break;
731
+ case PointInPolygonResult::IsInside:
732
+ if (pip == PointInPolygonResult::IsInside) return true;
733
+ pip = PointInPolygonResult::IsInside;
734
+ break;
735
+ default:
736
+ break;
737
+ }
738
+ }
739
+ if (pip != PointInPolygonResult::IsInside) return false;
740
+ // result is likely true but check midpoint
741
+ Point<T> mp1 = GetBounds(path1).MidPoint();
742
+ return PointInPolygon(mp1, path2) == PointInPolygonResult::IsInside;
743
+ }
744
+
745
+ template <typename T>
746
+ inline void RDP(const Path<T> path, std::size_t begin,
747
+ std::size_t end, double epsSqrd, std::vector<bool>& flags)
748
+ {
749
+ typename Path<T>::size_type idx = 0;
750
+ double max_d = 0;
751
+ while (end > begin && path[begin] == path[end]) flags[end--] = false;
752
+ for (typename Path<T>::size_type i = begin + 1; i < end; ++i)
753
+ {
754
+ // PerpendicDistFromLineSqrd - avoids expensive Sqrt()
755
+ double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]);
756
+ if (d <= max_d) continue;
757
+ max_d = d;
758
+ idx = i;
759
+ }
760
+ if (max_d <= epsSqrd) return;
761
+ flags[idx] = true;
762
+ if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags);
763
+ if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags);
764
+ }
765
+
766
+ template <typename T>
767
+ inline Path<T> RamerDouglasPeucker(const Path<T>& path, double epsilon)
768
+ {
769
+ const typename Path<T>::size_type len = path.size();
770
+ if (len < 5) return Path<T>(path);
771
+ std::vector<bool> flags(len);
772
+ flags[0] = true;
773
+ flags[len - 1] = true;
774
+ RDP(path, 0, len - 1, Sqr(epsilon), flags);
775
+ Path<T> result;
776
+ result.reserve(len);
777
+ for (typename Path<T>::size_type i = 0; i < len; ++i)
778
+ if (flags[i])
779
+ result.emplace_back(path[i]);
780
+ return result;
781
+ }
782
+
783
+ template <typename T>
784
+ inline Paths<T> RamerDouglasPeucker(const Paths<T>& paths, double epsilon)
785
+ {
786
+ Paths<T> result;
787
+ result.reserve(paths.size());
788
+ std::transform(paths.begin(), paths.end(), back_inserter(result),
789
+ [epsilon](const auto& path)
790
+ { return RamerDouglasPeucker<T>(path, epsilon); });
791
+ return result;
792
+ }
793
+
794
+ } // end Clipper2Lib namespace
795
+
796
+ #endif // CLIPPER_H