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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +165 -0
- data/examples/larb_transform.rb +17 -0
- data/examples/rugl_mesh.rb +33 -0
- data/examples/svg_boolean.rb +28 -0
- data/ext/clipper2/CMakeLists.txt +27 -0
- data/ext/clipper2/clipper2_native.cpp +2 -0
- data/ext/clipper2/extconf.rb +27 -0
- data/ext/clipper2/generate_bindings.rb +73 -0
- data/ext/clipper2/memory_check.cpp +48 -0
- data/ext/clipper2/vendor/LICENSE +23 -0
- data/ext/clipper2/vendor/UPSTREAM.md +11 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.core.h +1159 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.engine.h +635 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.export.h +851 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.h +796 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.minkowski.h +117 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.offset.h +125 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.rectclip.h +80 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.triangulation.h +27 -0
- data/ext/clipper2/vendor/include/clipper2/clipper.version.h +6 -0
- data/ext/clipper2/vendor/src/clipper.engine.cpp +3152 -0
- data/ext/clipper2/vendor/src/clipper.offset.cpp +661 -0
- data/ext/clipper2/vendor/src/clipper.rectclip.cpp +1027 -0
- data/ext/clipper2/vendor/src/clipper.triangulation.cpp +1221 -0
- data/lib/clipper2/api.rb +45 -0
- data/lib/clipper2/c_paths64.rb +80 -0
- data/lib/clipper2/earcut/engine.rb +191 -0
- data/lib/clipper2/earcut/geometry.rb +100 -0
- data/lib/clipper2/earcut/node.rb +18 -0
- data/lib/clipper2/earcut/ring.rb +94 -0
- data/lib/clipper2/earcut.rb +18 -0
- data/lib/clipper2/errors.rb +19 -0
- data/lib/clipper2/generated.rb +62 -0
- data/lib/clipper2/native.rb +38 -0
- data/lib/clipper2/native_memory.rb +64 -0
- data/lib/clipper2/operations.rb +206 -0
- data/lib/clipper2/path.rb +171 -0
- data/lib/clipper2/path_simplifier.rb +61 -0
- data/lib/clipper2/paths.rb +130 -0
- data/lib/clipper2/quantizer.rb +48 -0
- data/lib/clipper2/triangulation.rb +103 -0
- data/lib/clipper2/version.rb +5 -0
- data/lib/clipper2.rb +34 -0
- data/licenses/CLIPPER2-BSL-1.0.txt +23 -0
- data/licenses/EARCUT-ISC.txt +15 -0
- metadata +113 -0
|
@@ -0,0 +1,1159 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
* Author : Angus Johnson *
|
|
3
|
+
* Date : 12 October 2025 *
|
|
4
|
+
* Website : https://www.angusj.com *
|
|
5
|
+
* Copyright : Angus Johnson 2010-2025 *
|
|
6
|
+
* Purpose : Core Clipper Library structures and functions *
|
|
7
|
+
* License : https://www.boost.org/LICENSE_1_0.txt *
|
|
8
|
+
*******************************************************************************/
|
|
9
|
+
|
|
10
|
+
#ifndef CLIPPER_CORE_H
|
|
11
|
+
#define CLIPPER_CORE_H
|
|
12
|
+
|
|
13
|
+
#include "clipper2/clipper.version.h"
|
|
14
|
+
#include <cstdint>
|
|
15
|
+
#include <vector>
|
|
16
|
+
#include <string>
|
|
17
|
+
#include <iostream>
|
|
18
|
+
#include <algorithm>
|
|
19
|
+
#include <numeric>
|
|
20
|
+
#include <cmath>
|
|
21
|
+
|
|
22
|
+
namespace Clipper2Lib
|
|
23
|
+
{
|
|
24
|
+
|
|
25
|
+
#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS)
|
|
26
|
+
|
|
27
|
+
class Clipper2Exception : public std::exception {
|
|
28
|
+
public:
|
|
29
|
+
explicit Clipper2Exception(const char* description) :
|
|
30
|
+
m_descr(description) {}
|
|
31
|
+
virtual const char* what() const noexcept override { return m_descr.c_str(); }
|
|
32
|
+
private:
|
|
33
|
+
std::string m_descr;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
static const char* precision_error =
|
|
37
|
+
"Precision exceeds the permitted range";
|
|
38
|
+
static const char* range_error =
|
|
39
|
+
"Values exceed permitted range";
|
|
40
|
+
static const char* scale_error =
|
|
41
|
+
"Invalid scale (either 0 or too large)";
|
|
42
|
+
static const char* non_pair_error =
|
|
43
|
+
"There must be 2 values for each coordinate";
|
|
44
|
+
static const char* undefined_error =
|
|
45
|
+
"There is an undefined error in Clipper2";
|
|
46
|
+
#endif
|
|
47
|
+
|
|
48
|
+
// error codes (2^n)
|
|
49
|
+
const int precision_error_i = 1; // non-fatal
|
|
50
|
+
const int scale_error_i = 2; // non-fatal
|
|
51
|
+
const int non_pair_error_i = 4; // non-fatal
|
|
52
|
+
const int undefined_error_i = 32; // fatal
|
|
53
|
+
const int range_error_i = 64;
|
|
54
|
+
|
|
55
|
+
#ifndef PI
|
|
56
|
+
static const double PI = 3.141592653589793238;
|
|
57
|
+
#endif
|
|
58
|
+
|
|
59
|
+
#ifdef CLIPPER2_MAX_DECIMAL_PRECISION
|
|
60
|
+
const int CLIPPER2_MAX_DEC_PRECISION = CLIPPER2_MAX_DECIMAL_PRECISION;
|
|
61
|
+
#else
|
|
62
|
+
const int CLIPPER2_MAX_DEC_PRECISION = 8; // see Discussions #564
|
|
63
|
+
#endif
|
|
64
|
+
|
|
65
|
+
static const int64_t MAX_COORD = INT64_MAX >> 2;
|
|
66
|
+
static const int64_t MIN_COORD = -MAX_COORD;
|
|
67
|
+
static const int64_t INVALID = INT64_MAX;
|
|
68
|
+
const double max_coord = static_cast<double>(MAX_COORD);
|
|
69
|
+
const double min_coord = static_cast<double>(MIN_COORD);
|
|
70
|
+
|
|
71
|
+
static const double MAX_DBL = (std::numeric_limits<double>::max)();
|
|
72
|
+
|
|
73
|
+
static void DoError([[maybe_unused]] int error_code)
|
|
74
|
+
{
|
|
75
|
+
#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS)
|
|
76
|
+
switch (error_code)
|
|
77
|
+
{
|
|
78
|
+
case precision_error_i:
|
|
79
|
+
throw Clipper2Exception(precision_error);
|
|
80
|
+
case scale_error_i:
|
|
81
|
+
throw Clipper2Exception(scale_error);
|
|
82
|
+
case non_pair_error_i:
|
|
83
|
+
throw Clipper2Exception(non_pair_error);
|
|
84
|
+
case undefined_error_i:
|
|
85
|
+
throw Clipper2Exception(undefined_error);
|
|
86
|
+
case range_error_i:
|
|
87
|
+
throw Clipper2Exception(range_error);
|
|
88
|
+
// Should never happen, but adding this to stop a compiler warning
|
|
89
|
+
default:
|
|
90
|
+
throw Clipper2Exception("Unknown error");
|
|
91
|
+
}
|
|
92
|
+
#else
|
|
93
|
+
++error_code; // only to stop compiler warning
|
|
94
|
+
#endif
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// can we call std::round on T? (default false) (#824)
|
|
98
|
+
template <typename T, typename = void>
|
|
99
|
+
struct is_round_invocable : std::false_type {};
|
|
100
|
+
|
|
101
|
+
template <typename T>
|
|
102
|
+
struct is_round_invocable<T, std::void_t<decltype(std::round(std::declval<T>()))>> : std::true_type {};
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
//By far the most widely used filling rules for polygons are EvenOdd
|
|
106
|
+
//and NonZero, sometimes called Alternate and Winding respectively.
|
|
107
|
+
//https://en.wikipedia.org/wiki/Nonzero-rule
|
|
108
|
+
enum class FillRule { EvenOdd, NonZero, Positive, Negative };
|
|
109
|
+
|
|
110
|
+
#ifdef USINGZ
|
|
111
|
+
using z_type = int64_t;
|
|
112
|
+
#endif
|
|
113
|
+
|
|
114
|
+
// Point ------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
template <typename T>
|
|
117
|
+
struct Point {
|
|
118
|
+
T x;
|
|
119
|
+
T y;
|
|
120
|
+
#ifdef USINGZ
|
|
121
|
+
z_type z;
|
|
122
|
+
|
|
123
|
+
template <typename T2>
|
|
124
|
+
inline void Init(const T2 x_ = 0, const T2 y_ = 0, const z_type z_ = 0)
|
|
125
|
+
{
|
|
126
|
+
if constexpr (std::is_integral_v<T> &&
|
|
127
|
+
is_round_invocable<T2>::value && !std::is_integral_v<T2>)
|
|
128
|
+
{
|
|
129
|
+
x = static_cast<T>(std::round(x_));
|
|
130
|
+
y = static_cast<T>(std::round(y_));
|
|
131
|
+
z = z_;
|
|
132
|
+
}
|
|
133
|
+
else
|
|
134
|
+
{
|
|
135
|
+
x = static_cast<T>(x_);
|
|
136
|
+
y = static_cast<T>(y_);
|
|
137
|
+
z = z_;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
explicit Point() : x(0), y(0), z(0) {}
|
|
142
|
+
|
|
143
|
+
template <typename T2>
|
|
144
|
+
Point(const T2 x_, const T2 y_, const z_type z_ = 0)
|
|
145
|
+
{
|
|
146
|
+
Init(x_, y_);
|
|
147
|
+
z = z_;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
template <typename T2>
|
|
151
|
+
explicit Point(const Point<T2>& p)
|
|
152
|
+
{
|
|
153
|
+
Init(p.x, p.y, p.z);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
template <typename T2>
|
|
157
|
+
explicit Point(const Point<T2>& p, z_type z_)
|
|
158
|
+
{
|
|
159
|
+
Init(p.x, p.y, z_);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
Point operator * (const double scale) const
|
|
163
|
+
{
|
|
164
|
+
return Point(x * scale, y * scale, z);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
void SetZ(const z_type z_value) { z = z_value; }
|
|
168
|
+
|
|
169
|
+
friend std::ostream& operator<<(std::ostream& os, const Point& point)
|
|
170
|
+
{
|
|
171
|
+
os << point.x << "," << point.y << "," << point.z;
|
|
172
|
+
return os;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#else
|
|
176
|
+
|
|
177
|
+
template <typename T2>
|
|
178
|
+
inline void Init(const T2 x_ = 0, const T2 y_ = 0)
|
|
179
|
+
{
|
|
180
|
+
if constexpr (std::is_integral_v<T> &&
|
|
181
|
+
is_round_invocable<T2>::value && !std::is_integral_v<T2>)
|
|
182
|
+
{
|
|
183
|
+
x = static_cast<T>(std::round(x_));
|
|
184
|
+
y = static_cast<T>(std::round(y_));
|
|
185
|
+
}
|
|
186
|
+
else
|
|
187
|
+
{
|
|
188
|
+
x = static_cast<T>(x_);
|
|
189
|
+
y = static_cast<T>(y_);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
explicit Point() : x(0), y(0) {}
|
|
194
|
+
|
|
195
|
+
template <typename T2>
|
|
196
|
+
Point(const T2 x_, const T2 y_) { Init(x_, y_); }
|
|
197
|
+
|
|
198
|
+
template <typename T2>
|
|
199
|
+
explicit Point(const Point<T2>& p) { Init(p.x, p.y); }
|
|
200
|
+
|
|
201
|
+
Point operator * (const double scale) const
|
|
202
|
+
{
|
|
203
|
+
return Point(x * scale, y * scale);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
friend std::ostream& operator<<(std::ostream& os, const Point& point)
|
|
207
|
+
{
|
|
208
|
+
os << point.x << "," << point.y;
|
|
209
|
+
return os;
|
|
210
|
+
}
|
|
211
|
+
#endif
|
|
212
|
+
|
|
213
|
+
friend bool operator==(const Point& a, const Point& b)
|
|
214
|
+
{
|
|
215
|
+
return a.x == b.x && a.y == b.y;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
friend bool operator!=(const Point& a, const Point& b)
|
|
219
|
+
{
|
|
220
|
+
return !(a == b);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
inline Point<T> operator-() const
|
|
224
|
+
{
|
|
225
|
+
return Point<T>(-x, -y);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
inline Point operator+(const Point& b) const
|
|
229
|
+
{
|
|
230
|
+
return Point(x + b.x, y + b.y);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
inline Point operator-(const Point& b) const
|
|
234
|
+
{
|
|
235
|
+
return Point(x - b.x, y - b.y);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
inline void Negate() { x = -x; y = -y; }
|
|
239
|
+
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
//nb: using 'using' here (instead of typedef) as they can be used in templates
|
|
243
|
+
using Point64 = Point<int64_t>;
|
|
244
|
+
using PointD = Point<double>;
|
|
245
|
+
|
|
246
|
+
template <typename T>
|
|
247
|
+
using Path = std::vector<Point<T>>;
|
|
248
|
+
template <typename T>
|
|
249
|
+
using Paths = std::vector<Path<T>>;
|
|
250
|
+
|
|
251
|
+
template <typename T, typename T2=T>
|
|
252
|
+
Path<T>& operator<<(Path<T>& poly, const Point<T2>& p)
|
|
253
|
+
{
|
|
254
|
+
poly.emplace_back(p);
|
|
255
|
+
return poly;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
template <typename T>
|
|
259
|
+
Paths<T>& operator<<(Paths<T>& polys, const Path<T>& p)
|
|
260
|
+
{
|
|
261
|
+
polys.emplace_back(p);
|
|
262
|
+
return polys;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
using Path64 = Path<int64_t>;
|
|
266
|
+
using PathD = Path<double>;
|
|
267
|
+
using Paths64 = std::vector< Path64>;
|
|
268
|
+
using PathsD = std::vector< PathD>;
|
|
269
|
+
|
|
270
|
+
static const Point64 InvalidPoint64 = Point64(
|
|
271
|
+
(std::numeric_limits<int64_t>::max)(),
|
|
272
|
+
(std::numeric_limits<int64_t>::max)());
|
|
273
|
+
static const PointD InvalidPointD = PointD(
|
|
274
|
+
(std::numeric_limits<double>::max)(),
|
|
275
|
+
(std::numeric_limits<double>::max)());
|
|
276
|
+
|
|
277
|
+
template<typename T>
|
|
278
|
+
static inline Point<T> MidPoint(const Point<T>& p1, const Point<T>& p2)
|
|
279
|
+
{
|
|
280
|
+
Point<T> result;
|
|
281
|
+
result.x = (p1.x + p2.x) / 2;
|
|
282
|
+
result.y = (p1.y + p2.y) / 2;
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Rect ------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
template <typename T>
|
|
289
|
+
struct Rect;
|
|
290
|
+
|
|
291
|
+
using Rect64 = Rect<int64_t>;
|
|
292
|
+
using RectD = Rect<double>;
|
|
293
|
+
|
|
294
|
+
template <typename T>
|
|
295
|
+
struct Rect {
|
|
296
|
+
T left;
|
|
297
|
+
T top;
|
|
298
|
+
T right;
|
|
299
|
+
T bottom;
|
|
300
|
+
|
|
301
|
+
Rect(T l, T t, T r, T b) :
|
|
302
|
+
left(l),
|
|
303
|
+
top(t),
|
|
304
|
+
right(r),
|
|
305
|
+
bottom(b) {}
|
|
306
|
+
|
|
307
|
+
Rect(bool is_valid = true)
|
|
308
|
+
{
|
|
309
|
+
if (is_valid)
|
|
310
|
+
{
|
|
311
|
+
left = right = top = bottom = 0;
|
|
312
|
+
}
|
|
313
|
+
else
|
|
314
|
+
{
|
|
315
|
+
left = top = (std::numeric_limits<T>::max)();
|
|
316
|
+
right = bottom = std::numeric_limits<T>::lowest();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
static Rect<T> InvalidRect()
|
|
321
|
+
{
|
|
322
|
+
return {
|
|
323
|
+
(std::numeric_limits<T>::max)(),
|
|
324
|
+
(std::numeric_limits<T>::max)(),
|
|
325
|
+
std::numeric_limits<T>::lowest(),
|
|
326
|
+
std::numeric_limits<T>::lowest() };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
bool IsValid() const { return left != (std::numeric_limits<T>::max)(); }
|
|
330
|
+
|
|
331
|
+
T Width() const { return right - left; }
|
|
332
|
+
T Height() const { return bottom - top; }
|
|
333
|
+
void Width(T width) { right = left + width; }
|
|
334
|
+
void Height(T height) { bottom = top + height; }
|
|
335
|
+
|
|
336
|
+
Point<T> MidPoint() const
|
|
337
|
+
{
|
|
338
|
+
return Point<T>((left + right) / 2, (top + bottom) / 2);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
Path<T> AsPath() const
|
|
342
|
+
{
|
|
343
|
+
Path<T> result;
|
|
344
|
+
result.reserve(4);
|
|
345
|
+
result.emplace_back(left, top);
|
|
346
|
+
result.emplace_back(right, top);
|
|
347
|
+
result.emplace_back(right, bottom);
|
|
348
|
+
result.emplace_back(left, bottom);
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
bool Contains(const Point<T>& pt) const
|
|
353
|
+
{
|
|
354
|
+
return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
bool Contains(const Rect<T>& rec) const
|
|
358
|
+
{
|
|
359
|
+
return rec.left >= left && rec.right <= right &&
|
|
360
|
+
rec.top >= top && rec.bottom <= bottom;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
void Scale(double scale) {
|
|
364
|
+
left *= scale;
|
|
365
|
+
top *= scale;
|
|
366
|
+
right *= scale;
|
|
367
|
+
bottom *= scale;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
bool IsEmpty() const { return bottom <= top || right <= left; }
|
|
371
|
+
|
|
372
|
+
bool Intersects(const Rect<T>& rec) const
|
|
373
|
+
{
|
|
374
|
+
return ((std::max)(left, rec.left) <= (std::min)(right, rec.right)) &&
|
|
375
|
+
((std::max)(top, rec.top) <= (std::min)(bottom, rec.bottom));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
bool operator==(const Rect<T>& other) const {
|
|
379
|
+
return left == other.left && right == other.right &&
|
|
380
|
+
top == other.top && bottom == other.bottom;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
Rect<T>& operator+=(const Rect<T>& other)
|
|
384
|
+
{
|
|
385
|
+
left = (std::min)(left, other.left);
|
|
386
|
+
top = (std::min)(top, other.top);
|
|
387
|
+
right = (std::max)(right, other.right);
|
|
388
|
+
bottom = (std::max)(bottom, other.bottom);
|
|
389
|
+
return *this;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
Rect<T> operator+(const Rect<T>& other) const
|
|
393
|
+
{
|
|
394
|
+
Rect<T> result = *this;
|
|
395
|
+
result += other;
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
friend std::ostream& operator<<(std::ostream& os, const Rect<T>& rect) {
|
|
400
|
+
os << "(" << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom << ") ";
|
|
401
|
+
return os;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
template <typename T1, typename T2>
|
|
406
|
+
inline Rect<T1> ScaleRect(const Rect<T2>& rect, double scale)
|
|
407
|
+
{
|
|
408
|
+
Rect<T1> result;
|
|
409
|
+
|
|
410
|
+
if constexpr (std::is_integral_v<T1> &&
|
|
411
|
+
is_round_invocable<T2>::value && !std::is_integral_v<T2>)
|
|
412
|
+
{
|
|
413
|
+
result.left = static_cast<T1>(std::round(rect.left * scale));
|
|
414
|
+
result.top = static_cast<T1>(std::round(rect.top * scale));
|
|
415
|
+
result.right = static_cast<T1>(std::round(rect.right * scale));
|
|
416
|
+
result.bottom = static_cast<T1>(std::round(rect.bottom * scale));
|
|
417
|
+
}
|
|
418
|
+
else
|
|
419
|
+
{
|
|
420
|
+
result.left = static_cast<T1>(rect.left * scale);
|
|
421
|
+
result.top = static_cast<T1>(rect.top * scale);
|
|
422
|
+
result.right = static_cast<T1>(rect.right * scale);
|
|
423
|
+
result.bottom = static_cast<T1>(rect.bottom * scale);
|
|
424
|
+
}
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
static const Rect64 InvalidRect64 = Rect64::InvalidRect();
|
|
429
|
+
static const RectD InvalidRectD = RectD::InvalidRect();
|
|
430
|
+
|
|
431
|
+
template <typename T>
|
|
432
|
+
Rect<T> GetBounds(const Path<T>& path)
|
|
433
|
+
{
|
|
434
|
+
T xmin = (std::numeric_limits<T>::max)();
|
|
435
|
+
T ymin = (std::numeric_limits<T>::max)();
|
|
436
|
+
T xmax = std::numeric_limits<T>::lowest();
|
|
437
|
+
T ymax = std::numeric_limits<T>::lowest();
|
|
438
|
+
for (const auto& p : path)
|
|
439
|
+
{
|
|
440
|
+
if (p.x < xmin) xmin = p.x;
|
|
441
|
+
if (p.x > xmax) xmax = p.x;
|
|
442
|
+
if (p.y < ymin) ymin = p.y;
|
|
443
|
+
if (p.y > ymax) ymax = p.y;
|
|
444
|
+
}
|
|
445
|
+
return Rect<T>(xmin, ymin, xmax, ymax);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
template <typename T>
|
|
449
|
+
Rect<T> GetBounds(const Paths<T>& paths)
|
|
450
|
+
{
|
|
451
|
+
T xmin = (std::numeric_limits<T>::max)();
|
|
452
|
+
T ymin = (std::numeric_limits<T>::max)();
|
|
453
|
+
T xmax = std::numeric_limits<T>::lowest();
|
|
454
|
+
T ymax = std::numeric_limits<T>::lowest();
|
|
455
|
+
for (const Path<T>& path : paths)
|
|
456
|
+
for (const Point<T>& p : path)
|
|
457
|
+
{
|
|
458
|
+
if (p.x < xmin) xmin = p.x;
|
|
459
|
+
if (p.x > xmax) xmax = p.x;
|
|
460
|
+
if (p.y < ymin) ymin = p.y;
|
|
461
|
+
if (p.y > ymax) ymax = p.y;
|
|
462
|
+
}
|
|
463
|
+
return Rect<T>(xmin, ymin, xmax, ymax);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
template <typename T, typename T2>
|
|
467
|
+
Rect<T> GetBounds(const Path<T2>& path)
|
|
468
|
+
{
|
|
469
|
+
T xmin = (std::numeric_limits<T>::max)();
|
|
470
|
+
T ymin = (std::numeric_limits<T>::max)();
|
|
471
|
+
T xmax = std::numeric_limits<T>::lowest();
|
|
472
|
+
T ymax = std::numeric_limits<T>::lowest();
|
|
473
|
+
for (const auto& p : path)
|
|
474
|
+
{
|
|
475
|
+
if (p.x < xmin) xmin = static_cast<T>(p.x);
|
|
476
|
+
if (p.x > xmax) xmax = static_cast<T>(p.x);
|
|
477
|
+
if (p.y < ymin) ymin = static_cast<T>(p.y);
|
|
478
|
+
if (p.y > ymax) ymax = static_cast<T>(p.y);
|
|
479
|
+
}
|
|
480
|
+
return Rect<T>(xmin, ymin, xmax, ymax);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
template <typename T, typename T2>
|
|
484
|
+
Rect<T> GetBounds(const Paths<T2>& paths)
|
|
485
|
+
{
|
|
486
|
+
T xmin = (std::numeric_limits<T>::max)();
|
|
487
|
+
T ymin = (std::numeric_limits<T>::max)();
|
|
488
|
+
T xmax = std::numeric_limits<T>::lowest();
|
|
489
|
+
T ymax = std::numeric_limits<T>::lowest();
|
|
490
|
+
for (const Path<T2>& path : paths)
|
|
491
|
+
for (const Point<T2>& p : path)
|
|
492
|
+
{
|
|
493
|
+
if (p.x < xmin) xmin = static_cast<T>(p.x);
|
|
494
|
+
if (p.x > xmax) xmax = static_cast<T>(p.x);
|
|
495
|
+
if (p.y < ymin) ymin = static_cast<T>(p.y);
|
|
496
|
+
if (p.y > ymax) ymax = static_cast<T>(p.y);
|
|
497
|
+
}
|
|
498
|
+
return Rect<T>(xmin, ymin, xmax, ymax);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
template <typename T>
|
|
502
|
+
std::ostream& operator << (std::ostream& outstream, const Path<T>& path)
|
|
503
|
+
{
|
|
504
|
+
if (!path.empty())
|
|
505
|
+
{
|
|
506
|
+
auto pt = path.cbegin(), last = path.cend() - 1;
|
|
507
|
+
while (pt != last)
|
|
508
|
+
outstream << *pt++ << ", ";
|
|
509
|
+
outstream << *last << std::endl;
|
|
510
|
+
}
|
|
511
|
+
return outstream;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
template <typename T>
|
|
515
|
+
std::ostream& operator << (std::ostream& outstream, const Paths<T>& paths)
|
|
516
|
+
{
|
|
517
|
+
for (auto p : paths)
|
|
518
|
+
outstream << p;
|
|
519
|
+
return outstream;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
template <typename T1, typename T2>
|
|
524
|
+
inline Path<T1> ScalePath(const Path<T2>& path,
|
|
525
|
+
double scale_x, double scale_y, int& error_code)
|
|
526
|
+
{
|
|
527
|
+
Path<T1> result;
|
|
528
|
+
if (scale_x == 0 || scale_y == 0)
|
|
529
|
+
{
|
|
530
|
+
error_code |= scale_error_i;
|
|
531
|
+
DoError(scale_error_i);
|
|
532
|
+
// if no exception, treat as non-fatal error
|
|
533
|
+
if (scale_x == 0) scale_x = 1.0;
|
|
534
|
+
if (scale_y == 0) scale_y = 1.0;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
result.reserve(path.size());
|
|
538
|
+
#ifdef USINGZ
|
|
539
|
+
std::transform(path.begin(), path.end(), back_inserter(result),
|
|
540
|
+
[scale_x, scale_y](const auto& pt)
|
|
541
|
+
{ return Point<T1>(pt.x * scale_x, pt.y * scale_y, pt.z); });
|
|
542
|
+
#else
|
|
543
|
+
std::transform(path.begin(), path.end(), back_inserter(result),
|
|
544
|
+
[scale_x, scale_y](const auto& pt)
|
|
545
|
+
{ return Point<T1>(pt.x * scale_x, pt.y * scale_y); });
|
|
546
|
+
#endif
|
|
547
|
+
return result;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
template <typename T1, typename T2>
|
|
551
|
+
inline Path<T1> ScalePath(const Path<T2>& path,
|
|
552
|
+
double scale, int& error_code)
|
|
553
|
+
{
|
|
554
|
+
return ScalePath<T1, T2>(path, scale, scale, error_code);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
template <typename T1, typename T2>
|
|
558
|
+
inline Paths<T1> ScalePaths(const Paths<T2>& paths,
|
|
559
|
+
double scale_x, double scale_y, int& error_code)
|
|
560
|
+
{
|
|
561
|
+
Paths<T1> result;
|
|
562
|
+
|
|
563
|
+
if constexpr (std::is_integral_v<T1>)
|
|
564
|
+
{
|
|
565
|
+
RectD r = GetBounds<double, T2>(paths);
|
|
566
|
+
if ((r.left * scale_x) < min_coord ||
|
|
567
|
+
(r.right * scale_x) > max_coord ||
|
|
568
|
+
(r.top * scale_y) < min_coord ||
|
|
569
|
+
(r.bottom * scale_y) > max_coord)
|
|
570
|
+
{
|
|
571
|
+
error_code |= range_error_i;
|
|
572
|
+
DoError(range_error_i);
|
|
573
|
+
return result; // empty path
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
result.reserve(paths.size());
|
|
578
|
+
std::transform(paths.begin(), paths.end(), back_inserter(result),
|
|
579
|
+
[=, &error_code](const auto& path)
|
|
580
|
+
{ return ScalePath<T1, T2>(path, scale_x, scale_y, error_code); });
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
template <typename T1, typename T2>
|
|
585
|
+
inline Paths<T1> ScalePaths(const Paths<T2>& paths,
|
|
586
|
+
double scale, int& error_code)
|
|
587
|
+
{
|
|
588
|
+
return ScalePaths<T1, T2>(paths, scale, scale, error_code);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
template <typename T1, typename T2>
|
|
592
|
+
inline Path<T1> TransformPath(const Path<T2>& path)
|
|
593
|
+
{
|
|
594
|
+
Path<T1> result;
|
|
595
|
+
result.reserve(path.size());
|
|
596
|
+
std::transform(path.cbegin(), path.cend(), std::back_inserter(result),
|
|
597
|
+
[](const Point<T2>& pt) {return Point<T1>(pt); });
|
|
598
|
+
return result;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
template <typename T1, typename T2>
|
|
602
|
+
inline Paths<T1> TransformPaths(const Paths<T2>& paths)
|
|
603
|
+
{
|
|
604
|
+
Paths<T1> result;
|
|
605
|
+
std::transform(paths.cbegin(), paths.cend(), std::back_inserter(result),
|
|
606
|
+
[](const Path<T2>& path) {return TransformPath<T1, T2>(path); });
|
|
607
|
+
return result;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
template<typename T>
|
|
611
|
+
inline double Sqr(T val)
|
|
612
|
+
{
|
|
613
|
+
return static_cast<double>(val) * static_cast<double>(val);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
template<typename T>
|
|
617
|
+
inline bool NearEqual(const Point<T>& p1,
|
|
618
|
+
const Point<T>& p2, double max_dist_sqrd)
|
|
619
|
+
{
|
|
620
|
+
return Sqr(p1.x - p2.x) + Sqr(p1.y - p2.y) < max_dist_sqrd;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
template<typename T>
|
|
624
|
+
inline Path<T> StripNearEqual(const Path<T>& path,
|
|
625
|
+
double max_dist_sqrd, bool is_closed_path)
|
|
626
|
+
{
|
|
627
|
+
if (path.size() == 0) return Path<T>();
|
|
628
|
+
Path<T> result;
|
|
629
|
+
result.reserve(path.size());
|
|
630
|
+
typename Path<T>::const_iterator path_iter = path.cbegin();
|
|
631
|
+
Point<T> first_pt = *path_iter++, last_pt = first_pt;
|
|
632
|
+
result.emplace_back(first_pt);
|
|
633
|
+
for (; path_iter != path.cend(); ++path_iter)
|
|
634
|
+
{
|
|
635
|
+
if (!NearEqual(*path_iter, last_pt, max_dist_sqrd))
|
|
636
|
+
{
|
|
637
|
+
last_pt = *path_iter;
|
|
638
|
+
result.emplace_back(last_pt);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (!is_closed_path) return result;
|
|
642
|
+
while (result.size() > 1 &&
|
|
643
|
+
NearEqual(result.back(), first_pt, max_dist_sqrd)) result.pop_back();
|
|
644
|
+
return result;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
template<typename T>
|
|
648
|
+
inline Paths<T> StripNearEqual(const Paths<T>& paths,
|
|
649
|
+
double max_dist_sqrd, bool is_closed_path)
|
|
650
|
+
{
|
|
651
|
+
Paths<T> result;
|
|
652
|
+
result.reserve(paths.size());
|
|
653
|
+
for (typename Paths<T>::const_iterator paths_citer = paths.cbegin();
|
|
654
|
+
paths_citer != paths.cend(); ++paths_citer)
|
|
655
|
+
{
|
|
656
|
+
result.emplace_back(std::move(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path)));
|
|
657
|
+
}
|
|
658
|
+
return result;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
template<typename T>
|
|
662
|
+
inline void StripDuplicates( Path<T>& path, bool is_closed_path)
|
|
663
|
+
{
|
|
664
|
+
//https://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector#:~:text=Let%27s%20compare%20three%20approaches%3A
|
|
665
|
+
path.erase(std::unique(path.begin(), path.end()), path.end());
|
|
666
|
+
if (is_closed_path)
|
|
667
|
+
while (path.size() > 1 && path.back() == path.front()) path.pop_back();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
template<typename T>
|
|
671
|
+
inline void StripDuplicates( Paths<T>& paths, bool is_closed_path)
|
|
672
|
+
{
|
|
673
|
+
for (typename Paths<T>::iterator paths_citer = paths.begin();
|
|
674
|
+
paths_citer != paths.end(); ++paths_citer)
|
|
675
|
+
{
|
|
676
|
+
StripDuplicates(*paths_citer, is_closed_path);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Miscellaneous ------------------------------------------------------------
|
|
681
|
+
|
|
682
|
+
inline void CheckPrecisionRange(int& precision, int& error_code)
|
|
683
|
+
{
|
|
684
|
+
if (precision >= -CLIPPER2_MAX_DEC_PRECISION &&
|
|
685
|
+
precision <= CLIPPER2_MAX_DEC_PRECISION) return;
|
|
686
|
+
error_code |= precision_error_i; // non-fatal error
|
|
687
|
+
DoError(precision_error_i); // does nothing when exceptions are disabled
|
|
688
|
+
precision = precision > 0 ? CLIPPER2_MAX_DEC_PRECISION : -CLIPPER2_MAX_DEC_PRECISION;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
inline void CheckPrecisionRange(int& precision)
|
|
692
|
+
{
|
|
693
|
+
int error_code = 0;
|
|
694
|
+
CheckPrecisionRange(precision, error_code);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
inline int TriSign(int64_t x) // returns 0, 1 or -1
|
|
698
|
+
{
|
|
699
|
+
return (x > 0) - (x < 0);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
struct UInt128Struct
|
|
703
|
+
{
|
|
704
|
+
const uint64_t lo = 0;
|
|
705
|
+
const uint64_t hi = 0;
|
|
706
|
+
|
|
707
|
+
bool operator==(const UInt128Struct& other) const
|
|
708
|
+
{
|
|
709
|
+
return lo == other.lo && hi == other.hi;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
inline UInt128Struct MultiplyUInt64(uint64_t a, uint64_t b) // #834, #835
|
|
715
|
+
{
|
|
716
|
+
// note to self - lamba expressions follow
|
|
717
|
+
const auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
|
|
718
|
+
const auto hi = [](uint64_t x) { return x >> 32; };
|
|
719
|
+
|
|
720
|
+
const uint64_t x1 = lo(a) * lo(b);
|
|
721
|
+
const uint64_t x2 = hi(a) * lo(b) + hi(x1);
|
|
722
|
+
const uint64_t x3 = lo(a) * hi(b) + lo(x2);
|
|
723
|
+
return { uint64_t(lo(x3) << 32 | lo(x1)), uint64_t(hi(a) * hi(b) + hi(x2) + hi(x3)) };
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// returns true if (and only if) a * b == c * d
|
|
727
|
+
inline bool ProductsAreEqual(int64_t a, int64_t b, int64_t c, int64_t d)
|
|
728
|
+
{
|
|
729
|
+
#if (defined(__clang__) || defined(__GNUC__)) && UINTPTR_MAX >= UINT64_MAX
|
|
730
|
+
const auto ab = static_cast<__int128_t>(a) * static_cast<__int128_t>(b);
|
|
731
|
+
const auto cd = static_cast<__int128_t>(c) * static_cast<__int128_t>(d);
|
|
732
|
+
return ab == cd;
|
|
733
|
+
#else
|
|
734
|
+
// nb: unsigned values needed for calculating overflow carry
|
|
735
|
+
const auto abs_a = static_cast<uint64_t>(std::abs(a));
|
|
736
|
+
const auto abs_b = static_cast<uint64_t>(std::abs(b));
|
|
737
|
+
const auto abs_c = static_cast<uint64_t>(std::abs(c));
|
|
738
|
+
const auto abs_d = static_cast<uint64_t>(std::abs(d));
|
|
739
|
+
|
|
740
|
+
const auto ab = MultiplyUInt64(abs_a, abs_b);
|
|
741
|
+
const auto cd = MultiplyUInt64(abs_c, abs_d);
|
|
742
|
+
|
|
743
|
+
// nb: it's important to differentiate 0 values here from other values
|
|
744
|
+
const auto sign_ab = TriSign(a) * TriSign(b);
|
|
745
|
+
const auto sign_cd = TriSign(c) * TriSign(d);
|
|
746
|
+
|
|
747
|
+
return ab == cd && sign_ab == sign_cd;
|
|
748
|
+
#endif
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
template <typename T>
|
|
752
|
+
inline int CrossProductSign(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3)
|
|
753
|
+
{
|
|
754
|
+
const auto a = pt2.x - pt1.x;
|
|
755
|
+
const auto b = pt3.y - pt2.y;
|
|
756
|
+
const auto c = pt2.y - pt1.y;
|
|
757
|
+
const auto d = pt3.x - pt2.x;
|
|
758
|
+
|
|
759
|
+
#if (defined(__clang__) || defined(__GNUC__)) && UINTPTR_MAX >= UINT64_MAX
|
|
760
|
+
const auto ab = static_cast<__int128_t>(a) * static_cast<__int128_t>(b);
|
|
761
|
+
const auto cd = static_cast<__int128_t>(c) * static_cast<__int128_t>(d);
|
|
762
|
+
if (ab > cd) return 1;
|
|
763
|
+
else if (ab < cd) return -1;
|
|
764
|
+
else return 0;
|
|
765
|
+
#else
|
|
766
|
+
const auto ab = MultiplyUInt64(std::abs(a), std::abs(b));
|
|
767
|
+
const auto cd = MultiplyUInt64(std::abs(c), std::abs(d));
|
|
768
|
+
|
|
769
|
+
const auto sign_ab = TriSign(a) * TriSign(b);
|
|
770
|
+
const auto sign_cd = TriSign(c) * TriSign(d);
|
|
771
|
+
|
|
772
|
+
if (sign_ab == sign_cd)
|
|
773
|
+
{
|
|
774
|
+
int result;
|
|
775
|
+
if (ab.hi == cd.hi)
|
|
776
|
+
{
|
|
777
|
+
if (ab.lo == cd.lo) return 0;
|
|
778
|
+
result = (ab.lo > cd.lo) ? 1 : -1;
|
|
779
|
+
}
|
|
780
|
+
else result = (ab.hi > cd.hi) ? 1 : -1;
|
|
781
|
+
return (sign_ab > 0) ? result : -result;
|
|
782
|
+
}
|
|
783
|
+
return (sign_ab > sign_cd) ? 1 : -1;
|
|
784
|
+
#endif
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
template <typename T>
|
|
788
|
+
inline bool IsCollinear(const Point<T>& pt1,
|
|
789
|
+
const Point<T>& sharedPt, const Point<T>& pt2) // #777
|
|
790
|
+
{
|
|
791
|
+
const auto a = sharedPt.x - pt1.x;
|
|
792
|
+
const auto b = pt2.y - sharedPt.y;
|
|
793
|
+
const auto c = sharedPt.y - pt1.y;
|
|
794
|
+
const auto d = pt2.x - sharedPt.x;
|
|
795
|
+
// When checking for collinearity with very large coordinate values
|
|
796
|
+
// then ProductsAreEqual is more accurate than using CrossProduct.
|
|
797
|
+
return ProductsAreEqual(a, b, c, d);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
template <typename T>
|
|
802
|
+
inline double CrossProduct(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3) {
|
|
803
|
+
return (static_cast<double>(pt2.x - pt1.x) * static_cast<double>(pt3.y -
|
|
804
|
+
pt2.y) - static_cast<double>(pt2.y - pt1.y) * static_cast<double>(pt3.x - pt2.x));
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
template <typename T>
|
|
808
|
+
inline double CrossProduct(const Point<T>& vec1, const Point<T>& vec2)
|
|
809
|
+
{
|
|
810
|
+
return static_cast<double>(vec1.y * vec2.x) - static_cast<double>(vec2.y * vec1.x);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
template <typename T>
|
|
814
|
+
inline double DotProduct(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3) {
|
|
815
|
+
return (static_cast<double>(pt2.x - pt1.x) * static_cast<double>(pt3.x - pt2.x) +
|
|
816
|
+
static_cast<double>(pt2.y - pt1.y) * static_cast<double>(pt3.y - pt2.y));
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
template <typename T>
|
|
820
|
+
inline double DotProduct(const Point<T>& vec1, const Point<T>& vec2)
|
|
821
|
+
{
|
|
822
|
+
return static_cast<double>(vec1.x * vec2.x) + static_cast<double>(vec1.y * vec2.y);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
template <typename T>
|
|
826
|
+
inline double DistanceSqr(const Point<T> pt1, const Point<T> pt2)
|
|
827
|
+
{
|
|
828
|
+
return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
template <typename T>
|
|
832
|
+
inline double PerpendicDistFromLineSqrd(const Point<T>& pt,
|
|
833
|
+
const Point<T>& line1, const Point<T>& line2)
|
|
834
|
+
{
|
|
835
|
+
//perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²)
|
|
836
|
+
//see https://en.wikipedia.org/wiki/Perpendicular_distance
|
|
837
|
+
double a = static_cast<double>(pt.x - line1.x);
|
|
838
|
+
double b = static_cast<double>(pt.y - line1.y);
|
|
839
|
+
double c = static_cast<double>(line2.x - line1.x);
|
|
840
|
+
double d = static_cast<double>(line2.y - line1.y);
|
|
841
|
+
if (c == 0 && d == 0) return 0;
|
|
842
|
+
return Sqr(a * d - c * b) / (c * c + d * d);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
template <typename T>
|
|
846
|
+
inline double Area(const Path<T>& path)
|
|
847
|
+
{
|
|
848
|
+
size_t cnt = path.size();
|
|
849
|
+
if (cnt < 3) return 0.0;
|
|
850
|
+
double a = 0.0;
|
|
851
|
+
typename Path<T>::const_iterator it1, it2 = path.cend() - 1, stop = it2;
|
|
852
|
+
if (!(cnt & 1)) ++stop;
|
|
853
|
+
for (it1 = path.cbegin(); it1 != stop;)
|
|
854
|
+
{
|
|
855
|
+
a += static_cast<double>(it2->y + it1->y) * (it2->x - it1->x);
|
|
856
|
+
it2 = it1 + 1;
|
|
857
|
+
a += static_cast<double>(it1->y + it2->y) * (it1->x - it2->x);
|
|
858
|
+
it1 += 2;
|
|
859
|
+
}
|
|
860
|
+
if (cnt & 1)
|
|
861
|
+
a += static_cast<double>(it2->y + it1->y) * (it2->x - it1->x);
|
|
862
|
+
return (a * 0.5);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
template <typename T>
|
|
866
|
+
inline double Area(const Paths<T>& paths)
|
|
867
|
+
{
|
|
868
|
+
double a = 0.0;
|
|
869
|
+
for (typename Paths<T>::const_iterator paths_iter = paths.cbegin();
|
|
870
|
+
paths_iter != paths.cend(); ++paths_iter)
|
|
871
|
+
{
|
|
872
|
+
a += Area<T>(*paths_iter);
|
|
873
|
+
}
|
|
874
|
+
return a;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
template <typename T>
|
|
878
|
+
inline bool IsPositive(const Path<T>& poly)
|
|
879
|
+
{
|
|
880
|
+
// A curve has positive orientation [and area] if a region 'R'
|
|
881
|
+
// is on the left when traveling around the outside of 'R'.
|
|
882
|
+
//https://mathworld.wolfram.com/CurveOrientation.html
|
|
883
|
+
//nb: This statement is premised on using Cartesian coordinates
|
|
884
|
+
return Area<T>(poly) >= 0;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// GetLineIntersectPt - a 'true' result is non-parallel. The 'ip' will also
|
|
888
|
+
// be constrained to seg1. However, it's possible that 'ip' won't be inside
|
|
889
|
+
// seg2, even when 'ip' hasn't been constrained (ie 'ip' is inside seg1).
|
|
890
|
+
|
|
891
|
+
#if defined(CLIPPER2_HI_PRECISION) && CLIPPER2_HI_PRECISION
|
|
892
|
+
// caution: this will compromise performance
|
|
893
|
+
// https://github.com/AngusJohnson/Clipper2/issues/317#issuecomment-1314023253
|
|
894
|
+
// See also CPP/BenchMark/GetIntersectPtBenchmark.cpp
|
|
895
|
+
#define CC_MIN(x,y) ((x)>(y)?(y):(x))
|
|
896
|
+
#define CC_MAX(x,y) ((x)<(y)?(y):(x))
|
|
897
|
+
template<typename T>
|
|
898
|
+
inline bool GetLineIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
|
899
|
+
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
|
|
900
|
+
{
|
|
901
|
+
double ln1dy = static_cast<double>(ln1b.y - ln1a.y);
|
|
902
|
+
double ln1dx = static_cast<double>(ln1a.x - ln1b.x);
|
|
903
|
+
double ln2dy = static_cast<double>(ln2b.y - ln2a.y);
|
|
904
|
+
double ln2dx = static_cast<double>(ln2a.x - ln2b.x);
|
|
905
|
+
double det = (ln2dy * ln1dx) - (ln1dy * ln2dx);
|
|
906
|
+
if (det == 0.0) return false;
|
|
907
|
+
T bb0minx = CC_MIN(ln1a.x, ln1b.x);
|
|
908
|
+
T bb0miny = CC_MIN(ln1a.y, ln1b.y);
|
|
909
|
+
T bb0maxx = CC_MAX(ln1a.x, ln1b.x);
|
|
910
|
+
T bb0maxy = CC_MAX(ln1a.y, ln1b.y);
|
|
911
|
+
T bb1minx = CC_MIN(ln2a.x, ln2b.x);
|
|
912
|
+
T bb1miny = CC_MIN(ln2a.y, ln2b.y);
|
|
913
|
+
T bb1maxx = CC_MAX(ln2a.x, ln2b.x);
|
|
914
|
+
T bb1maxy = CC_MAX(ln2a.y, ln2b.y);
|
|
915
|
+
|
|
916
|
+
if constexpr (std::is_integral_v<T>)
|
|
917
|
+
{
|
|
918
|
+
int64_t originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) >> 1;
|
|
919
|
+
int64_t originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) >> 1;
|
|
920
|
+
double ln0c = (ln1dy * static_cast<double>(ln1a.x - originx)) +
|
|
921
|
+
(ln1dx * static_cast<double>(ln1a.y - originy));
|
|
922
|
+
double ln1c = (ln2dy * static_cast<double>(ln2a.x - originx)) +
|
|
923
|
+
(ln2dx * static_cast<double>(ln2a.y - originy));
|
|
924
|
+
double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det;
|
|
925
|
+
double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det;
|
|
926
|
+
|
|
927
|
+
ip.x = originx + (T)nearbyint(hitx);
|
|
928
|
+
ip.y = originy + (T)nearbyint(hity);
|
|
929
|
+
}
|
|
930
|
+
else
|
|
931
|
+
{
|
|
932
|
+
double originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) / 2.0;
|
|
933
|
+
double originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) / 2.0;
|
|
934
|
+
double ln0c = (ln1dy * static_cast<double>(ln1a.x - originx)) +
|
|
935
|
+
(ln1dx * static_cast<double>(ln1a.y - originy));
|
|
936
|
+
double ln1c = (ln2dy * static_cast<double>(ln2a.x - originx)) +
|
|
937
|
+
(ln2dx * static_cast<double>(ln2a.y - originy));
|
|
938
|
+
double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det;
|
|
939
|
+
double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det;
|
|
940
|
+
|
|
941
|
+
ip.x = originx + static_cast<T>(hitx);
|
|
942
|
+
ip.y = originy + static_cast<T>(hity);
|
|
943
|
+
}
|
|
944
|
+
#ifdef USINGZ
|
|
945
|
+
ip.z = 0;
|
|
946
|
+
#endif
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
#else
|
|
950
|
+
template<typename T>
|
|
951
|
+
inline bool GetLineIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
|
952
|
+
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
|
|
953
|
+
{
|
|
954
|
+
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
|
|
955
|
+
double dx1 = static_cast<double>(ln1b.x - ln1a.x);
|
|
956
|
+
double dy1 = static_cast<double>(ln1b.y - ln1a.y);
|
|
957
|
+
double dx2 = static_cast<double>(ln2b.x - ln2a.x);
|
|
958
|
+
double dy2 = static_cast<double>(ln2b.y - ln2a.y);
|
|
959
|
+
|
|
960
|
+
double det = dy1 * dx2 - dy2 * dx1;
|
|
961
|
+
if (det == 0.0) return false;
|
|
962
|
+
double t = ((ln1a.x - ln2a.x) * dy2 - (ln1a.y - ln2a.y) * dx2) / det;
|
|
963
|
+
if (t <= 0.0) ip = ln1a;
|
|
964
|
+
else if (t >= 1.0) ip = ln1b;
|
|
965
|
+
else
|
|
966
|
+
{
|
|
967
|
+
ip.x = static_cast<T>(ln1a.x + t * dx1);
|
|
968
|
+
ip.y = static_cast<T>(ln1a.y + t * dy1);
|
|
969
|
+
#ifdef USINGZ
|
|
970
|
+
ip.z = 0;
|
|
971
|
+
#endif
|
|
972
|
+
}
|
|
973
|
+
return true;
|
|
974
|
+
}
|
|
975
|
+
#endif
|
|
976
|
+
|
|
977
|
+
template<typename T>
|
|
978
|
+
inline Point<T> TranslatePoint(const Point<T>& pt, double dx, double dy)
|
|
979
|
+
{
|
|
980
|
+
#ifdef USINGZ
|
|
981
|
+
return Point<T>(pt.x + dx, pt.y + dy, pt.z);
|
|
982
|
+
#else
|
|
983
|
+
return Point<T>(pt.x + dx, pt.y + dy);
|
|
984
|
+
#endif
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
template<typename T>
|
|
989
|
+
inline Point<T> ReflectPoint(const Point<T>& pt, const Point<T>& pivot)
|
|
990
|
+
{
|
|
991
|
+
#ifdef USINGZ
|
|
992
|
+
return Point<T>(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y), pt.z);
|
|
993
|
+
#else
|
|
994
|
+
return Point<T>(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
|
|
995
|
+
#endif
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
template<typename T>
|
|
999
|
+
inline int GetSign(const T& val)
|
|
1000
|
+
{
|
|
1001
|
+
if (!val) return 0;
|
|
1002
|
+
return (val > 0) ? 1 : -1;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b,
|
|
1006
|
+
const Point64& seg2a, const Point64& seg2b, bool inclusive = false)
|
|
1007
|
+
{
|
|
1008
|
+
double dy1 = static_cast<double>(seg1b.y - seg1a.y);
|
|
1009
|
+
double dx1 = static_cast<double>(seg1b.x - seg1a.x);
|
|
1010
|
+
double dy2 = static_cast<double>(seg2b.y - seg2a.y);
|
|
1011
|
+
double dx2 = static_cast<double>(seg2b.x - seg2a.x);
|
|
1012
|
+
double cp = dy1 * dx2 - dy2 * dx1;
|
|
1013
|
+
if (cp == 0) return false; // ie parallel segments
|
|
1014
|
+
|
|
1015
|
+
if (inclusive)
|
|
1016
|
+
{
|
|
1017
|
+
//result **includes** segments that touch at an end point
|
|
1018
|
+
double t = ((seg1a.x - seg2a.x) * dy2 - (seg1a.y - seg2a.y) * dx2);
|
|
1019
|
+
if (t == 0) return true;
|
|
1020
|
+
if (t > 0)
|
|
1021
|
+
{
|
|
1022
|
+
if (cp < 0 || t > cp) return false;
|
|
1023
|
+
}
|
|
1024
|
+
else if (cp > 0 || t < cp) return false; // false when t more neg. than cp
|
|
1025
|
+
|
|
1026
|
+
t = ((seg1a.x - seg2a.x) * dy1 - (seg1a.y - seg2a.y) * dx1);
|
|
1027
|
+
if (t == 0) return true;
|
|
1028
|
+
if (t > 0) return (cp > 0 && t <= cp);
|
|
1029
|
+
else return (cp < 0 && t >= cp); // true when t less neg. than cp
|
|
1030
|
+
}
|
|
1031
|
+
else
|
|
1032
|
+
{
|
|
1033
|
+
//result **excludes** segments that touch at an end point
|
|
1034
|
+
double t = ((seg1a.x - seg2a.x) * dy2 - (seg1a.y - seg2a.y) * dx2);
|
|
1035
|
+
if (t == 0) return false;
|
|
1036
|
+
if (t > 0)
|
|
1037
|
+
{
|
|
1038
|
+
if (cp < 0 || t >= cp) return false;
|
|
1039
|
+
}
|
|
1040
|
+
else if (cp > 0 || t <= cp ) return false; // false when t more neg. than cp
|
|
1041
|
+
|
|
1042
|
+
t = ((seg1a.x - seg2a.x) * dy1 - (seg1a.y - seg2a.y) * dx1);
|
|
1043
|
+
if (t == 0) return false;
|
|
1044
|
+
if (t > 0) return (cp > 0 && t < cp);
|
|
1045
|
+
else return (cp < 0 && t > cp); // true when t less neg. than cp
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
template<typename T>
|
|
1050
|
+
inline Point<T> GetClosestPointOnSegment(const Point<T>& offPt,
|
|
1051
|
+
const Point<T>& seg1, const Point<T>& seg2)
|
|
1052
|
+
{
|
|
1053
|
+
if (seg1.x == seg2.x && seg1.y == seg2.y) return seg1;
|
|
1054
|
+
double dx = static_cast<double>(seg2.x - seg1.x);
|
|
1055
|
+
double dy = static_cast<double>(seg2.y - seg1.y);
|
|
1056
|
+
double q =
|
|
1057
|
+
(static_cast<double>(offPt.x - seg1.x) * dx +
|
|
1058
|
+
static_cast<double>(offPt.y - seg1.y) * dy) /
|
|
1059
|
+
(Sqr(dx) + Sqr(dy));
|
|
1060
|
+
if (q < 0) q = 0; else if (q > 1) q = 1;
|
|
1061
|
+
if constexpr (std::is_integral_v<T>)
|
|
1062
|
+
return Point<T>(
|
|
1063
|
+
seg1.x + static_cast<T>(nearbyint(q * dx)),
|
|
1064
|
+
seg1.y + static_cast<T>(nearbyint(q * dy)));
|
|
1065
|
+
else
|
|
1066
|
+
return Point<T>(
|
|
1067
|
+
seg1.x + static_cast<T>(q * dx),
|
|
1068
|
+
seg1.y + static_cast<T>(q * dy));
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
enum class PointInPolygonResult { IsOn, IsInside, IsOutside };
|
|
1072
|
+
|
|
1073
|
+
template <typename T>
|
|
1074
|
+
inline PointInPolygonResult PointInPolygon(const Point<T>& pt, const Path<T>& polygon)
|
|
1075
|
+
{
|
|
1076
|
+
if (polygon.size() < 3)
|
|
1077
|
+
return PointInPolygonResult::IsOutside;
|
|
1078
|
+
|
|
1079
|
+
int val = 0;
|
|
1080
|
+
typename Path<T>::const_iterator cbegin = polygon.cbegin(), first = cbegin, curr, prev;
|
|
1081
|
+
typename Path<T>::const_iterator cend = polygon.cend();
|
|
1082
|
+
|
|
1083
|
+
while (first != cend && first->y == pt.y) ++first;
|
|
1084
|
+
if (first == cend) // not a proper polygon
|
|
1085
|
+
return PointInPolygonResult::IsOutside;
|
|
1086
|
+
|
|
1087
|
+
bool is_above = first->y < pt.y, starting_above = is_above;
|
|
1088
|
+
curr = first +1;
|
|
1089
|
+
while (true)
|
|
1090
|
+
{
|
|
1091
|
+
if (curr == cend)
|
|
1092
|
+
{
|
|
1093
|
+
if (cend == first || first == cbegin) break;
|
|
1094
|
+
cend = first;
|
|
1095
|
+
curr = cbegin;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
if (is_above)
|
|
1099
|
+
{
|
|
1100
|
+
while (curr != cend && curr->y < pt.y) ++curr;
|
|
1101
|
+
if (curr == cend) continue;
|
|
1102
|
+
}
|
|
1103
|
+
else
|
|
1104
|
+
{
|
|
1105
|
+
while (curr != cend && curr->y > pt.y) ++curr;
|
|
1106
|
+
if (curr == cend) continue;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
if (curr == cbegin)
|
|
1110
|
+
prev = polygon.cend() - 1; //nb: NOT cend (since might equal first)
|
|
1111
|
+
else
|
|
1112
|
+
prev = curr - 1;
|
|
1113
|
+
|
|
1114
|
+
if (curr->y == pt.y)
|
|
1115
|
+
{
|
|
1116
|
+
if (curr->x == pt.x ||
|
|
1117
|
+
(curr->y == prev->y &&
|
|
1118
|
+
((pt.x < prev->x) != (pt.x < curr->x))))
|
|
1119
|
+
return PointInPolygonResult::IsOn;
|
|
1120
|
+
++curr;
|
|
1121
|
+
if (curr == first) break;
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (pt.x < curr->x && pt.x < prev->x)
|
|
1126
|
+
{
|
|
1127
|
+
// we're only interested in edges crossing on the left
|
|
1128
|
+
}
|
|
1129
|
+
else if (pt.x > prev->x && pt.x > curr->x)
|
|
1130
|
+
val = 1 - val; // toggle val
|
|
1131
|
+
else
|
|
1132
|
+
{
|
|
1133
|
+
int d = CrossProductSign(*prev, *curr, pt);
|
|
1134
|
+
if (d == 0) return PointInPolygonResult::IsOn;
|
|
1135
|
+
if ((d < 0) == is_above) val = 1 - val;
|
|
1136
|
+
}
|
|
1137
|
+
is_above = !is_above;
|
|
1138
|
+
++curr;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
if (is_above != starting_above)
|
|
1142
|
+
{
|
|
1143
|
+
cend = polygon.cend();
|
|
1144
|
+
if (curr == cend) curr = cbegin;
|
|
1145
|
+
if (curr == cbegin) prev = cend - 1;
|
|
1146
|
+
else prev = curr - 1;
|
|
1147
|
+
int d = CrossProductSign(*prev, *curr, pt);
|
|
1148
|
+
if (d == 0) return PointInPolygonResult::IsOn;
|
|
1149
|
+
if ((d < 0) == is_above) val = 1 - val;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
return (val == 0) ?
|
|
1153
|
+
PointInPolygonResult::IsOutside :
|
|
1154
|
+
PointInPolygonResult::IsInside;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
} // namespace
|
|
1158
|
+
|
|
1159
|
+
#endif // CLIPPER_CORE_H
|