Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/clipper2/src/clipper.offset.cpp
9902 views
1
/*******************************************************************************
2
* Author : Angus Johnson *
3
* Date : 4 May 2025 *
4
* Website : https://www.angusj.com *
5
* Copyright : Angus Johnson 2010-2025 *
6
* Purpose : Path Offset (Inflate/Shrink) *
7
* License : https://www.boost.org/LICENSE_1_0.txt *
8
*******************************************************************************/
9
10
#include "clipper2/clipper.h"
11
#include "clipper2/clipper.offset.h"
12
13
namespace Clipper2Lib {
14
15
const double floating_point_tolerance = 1e-12;
16
17
// Clipper2 approximates arcs by using series of relatively short straight
18
//line segments. And logically, shorter line segments will produce better arc
19
// approximations. But very short segments can degrade performance, usually
20
// with little or no discernable improvement in curve quality. Very short
21
// segments can even detract from curve quality, due to the effects of integer
22
// rounding. Since there isn't an optimal number of line segments for any given
23
// arc radius (that perfectly balances curve approximation with performance),
24
// arc tolerance is user defined. Nevertheless, when the user doesn't define
25
// an arc tolerance (ie leaves alone the 0 default value), the calculated
26
// default arc tolerance (offset_radius / 500) generally produces good (smooth)
27
// arc approximations without producing excessively small segment lengths.
28
// See also: https://www.angusj.com/clipper2/Docs/Trigonometry.htm
29
const double arc_const = 0.002; // <-- 1/500
30
31
32
//------------------------------------------------------------------------------
33
// Miscellaneous methods
34
//------------------------------------------------------------------------------
35
36
void GetLowestClosedPathInfo(const Paths64& paths, std::optional<size_t>& idx, bool& is_neg_area)
37
{
38
idx.reset();
39
Point64 botPt = Point64(INT64_MAX, INT64_MIN);
40
for (size_t i = 0; i < paths.size(); ++i)
41
{
42
double a = MAX_DBL;
43
for (const Point64& pt : paths[i])
44
{
45
if ((pt.y < botPt.y) ||
46
((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
47
if (a == MAX_DBL)
48
{
49
a = Area(paths[i]);
50
if (a == 0) break; // invalid closed path, so break from inner loop
51
is_neg_area = a < 0;
52
}
53
idx = i;
54
botPt.x = pt.x;
55
botPt.y = pt.y;
56
}
57
}
58
}
59
60
inline double Hypot(double x, double y)
61
{
62
// given that this is an internal function, and given the x and y parameters
63
// will always be coordinate values (or the difference between coordinate values),
64
// x and y should always be within INT64_MIN to INT64_MAX. Consequently,
65
// there should be no risk that the following computation will overflow
66
// see https://stackoverflow.com/a/32436148/359538
67
return std::sqrt(x * x + y * y);
68
}
69
70
static PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
71
{
72
if (pt1 == pt2) return PointD(0.0, 0.0);
73
double dx = static_cast<double>(pt2.x - pt1.x);
74
double dy = static_cast<double>(pt2.y - pt1.y);
75
double inverse_hypot = 1.0 / Hypot(dx, dy);
76
dx *= inverse_hypot;
77
dy *= inverse_hypot;
78
return PointD(dy, -dx);
79
}
80
81
inline bool AlmostZero(double value, double epsilon = 0.001)
82
{
83
return std::fabs(value) < epsilon;
84
}
85
86
inline PointD NormalizeVector(const PointD& vec)
87
{
88
double h = Hypot(vec.x, vec.y);
89
if (AlmostZero(h)) return PointD(0,0);
90
double inverseHypot = 1 / h;
91
return PointD(vec.x * inverseHypot, vec.y * inverseHypot);
92
}
93
94
inline PointD GetAvgUnitVector(const PointD& vec1, const PointD& vec2)
95
{
96
return NormalizeVector(PointD(vec1.x + vec2.x, vec1.y + vec2.y));
97
}
98
99
inline bool IsClosedPath(EndType et)
100
{
101
return et == EndType::Polygon || et == EndType::Joined;
102
}
103
104
static inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
105
{
106
#ifdef USINGZ
107
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
108
#else
109
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
110
#endif
111
}
112
113
inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
114
{
115
#ifdef USINGZ
116
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
117
#else
118
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
119
#endif
120
}
121
122
inline void NegatePath(PathD& path)
123
{
124
for (PointD& pt : path)
125
{
126
pt.x = -pt.x;
127
pt.y = -pt.y;
128
#ifdef USINGZ
129
pt.z = pt.z;
130
#endif
131
}
132
}
133
134
135
//------------------------------------------------------------------------------
136
// ClipperOffset::Group methods
137
//------------------------------------------------------------------------------
138
139
ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type):
140
paths_in(_paths), join_type(_join_type), end_type(_end_type)
141
{
142
bool is_joined =
143
(end_type == EndType::Polygon) ||
144
(end_type == EndType::Joined);
145
for (Path64& p: paths_in)
146
StripDuplicates(p, is_joined);
147
148
if (end_type == EndType::Polygon)
149
{
150
bool is_neg_area;
151
GetLowestClosedPathInfo(paths_in, lowest_path_idx, is_neg_area);
152
// the lowermost path must be an outer path, so if its orientation is negative,
153
// then flag the whole group is 'reversed' (will negate delta etc.)
154
// as this is much more efficient than reversing every path.
155
is_reversed = lowest_path_idx.has_value() && is_neg_area;
156
}
157
else
158
{
159
lowest_path_idx.reset();
160
is_reversed = false;
161
}
162
}
163
164
//------------------------------------------------------------------------------
165
// ClipperOffset methods
166
//------------------------------------------------------------------------------
167
168
void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_)
169
{
170
groups_.emplace_back(Paths64(1, path), jt_, et_);
171
}
172
173
void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_)
174
{
175
if (paths.size() == 0) return;
176
groups_.emplace_back(paths, jt_, et_);
177
}
178
179
void ClipperOffset::BuildNormals(const Path64& path)
180
{
181
norms.clear();
182
norms.reserve(path.size());
183
if (path.size() == 0) return;
184
Path64::const_iterator path_iter, path_stop_iter = --path.cend();
185
for (path_iter = path.cbegin(); path_iter != path_stop_iter; ++path_iter)
186
norms.emplace_back(GetUnitNormal(*path_iter,*(path_iter +1)));
187
norms.emplace_back(GetUnitNormal(*path_stop_iter, *(path.cbegin())));
188
}
189
190
void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k)
191
{
192
PointD pt1, pt2;
193
if (j == k)
194
{
195
double abs_delta = std::abs(group_delta_);
196
#ifdef USINGZ
197
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y, path[j].z);
198
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y, path[j].z);
199
#else
200
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y);
201
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y);
202
#endif
203
}
204
else
205
{
206
#ifdef USINGZ
207
pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y, path[j].z);
208
pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y, path[j].z);
209
#else
210
pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y);
211
pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y);
212
#endif
213
}
214
path_out.emplace_back(pt1);
215
path_out.emplace_back(pt2);
216
}
217
218
void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
219
{
220
PointD vec;
221
if (j == k)
222
vec = PointD(norms[j].y, -norms[j].x);
223
else
224
vec = GetAvgUnitVector(
225
PointD(-norms[k].y, norms[k].x),
226
PointD(norms[j].y, -norms[j].x));
227
228
double abs_delta = std::abs(group_delta_);
229
230
// now offset the original vertex delta units along unit vector
231
PointD ptQ = PointD(path[j]);
232
ptQ = TranslatePoint(ptQ, abs_delta * vec.x, abs_delta * vec.y);
233
// get perpendicular vertices
234
PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x);
235
PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x);
236
// get 2 vertices along one edge offset
237
PointD pt3 = GetPerpendicD(path[k], norms[k], group_delta_);
238
if (j == k)
239
{
240
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
241
PointD pt = ptQ;
242
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
243
//get the second intersect point through reflecion
244
path_out.emplace_back(ReflectPoint(pt, ptQ));
245
path_out.emplace_back(pt);
246
}
247
else
248
{
249
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
250
PointD pt = ptQ;
251
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
252
path_out.emplace_back(pt);
253
//get the second intersect point through reflecion
254
path_out.emplace_back(ReflectPoint(pt, ptQ));
255
}
256
}
257
258
void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a)
259
{
260
double q = group_delta_ / (cos_a + 1);
261
#ifdef USINGZ
262
path_out.emplace_back(
263
path[j].x + (norms[k].x + norms[j].x) * q,
264
path[j].y + (norms[k].y + norms[j].y) * q,
265
path[j].z);
266
#else
267
path_out.emplace_back(
268
path[j].x + (norms[k].x + norms[j].x) * q,
269
path[j].y + (norms[k].y + norms[j].y) * q);
270
#endif
271
}
272
273
void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle)
274
{
275
if (deltaCallback64_) {
276
// when deltaCallback64_ is assigned, group_delta_ won't be constant,
277
// so we'll need to do the following calculations for *every* vertex.
278
double abs_delta = std::fabs(group_delta_);
279
double arcTol = (arc_tolerance_ > floating_point_tolerance ?
280
std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const);
281
double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
282
step_sin_ = std::sin(2 * PI / steps_per_360);
283
step_cos_ = std::cos(2 * PI / steps_per_360);
284
if (group_delta_ < 0.0) step_sin_ = -step_sin_;
285
steps_per_rad_ = steps_per_360 / (2 * PI);
286
}
287
288
Point64 pt = path[j];
289
PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
290
291
if (j == k) offsetVec.Negate();
292
#ifdef USINGZ
293
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z);
294
#else
295
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
296
#endif
297
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
298
for (int i = 1; i < steps; ++i) // ie 1 less than steps
299
{
300
offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
301
offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
302
#ifdef USINGZ
303
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z);
304
#else
305
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
306
#endif
307
}
308
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_));
309
}
310
311
void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size_t k)
312
{
313
// Let A = change in angle where edges join
314
// A == 0: ie no change in angle (flat join)
315
// A == PI: edges 'spike'
316
// sin(A) < 0: right turning
317
// cos(A) < 0: change in angle is more than 90 degree
318
319
if (path[j] == path[k]) return;
320
321
double sin_a = CrossProduct(norms[j], norms[k]);
322
double cos_a = DotProduct(norms[j], norms[k]);
323
if (sin_a > 1.0) sin_a = 1.0;
324
else if (sin_a < -1.0) sin_a = -1.0;
325
326
if (deltaCallback64_) {
327
group_delta_ = deltaCallback64_(path, norms, j, k);
328
if (group.is_reversed) group_delta_ = -group_delta_;
329
}
330
if (std::fabs(group_delta_) <= floating_point_tolerance)
331
{
332
path_out.emplace_back(path[j]);
333
return;
334
}
335
336
if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
337
{
338
// is concave
339
// by far the simplest way to construct concave joins, especially those joining very
340
// short segments, is to insert 3 points that produce negative regions. These regions
341
// will be removed later by the finishing union operation. This is also the best way
342
// to ensure that path reversals (ie over-shrunk paths) are removed.
343
#ifdef USINGZ
344
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_), path[j].z);
345
path_out.emplace_back(path[j]); // (#405, #873, #916)
346
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_), path[j].z);
347
#else
348
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_));
349
path_out.emplace_back(path[j]); // (#405, #873, #916)
350
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_));
351
#endif
352
}
353
else if (cos_a > 0.999 && join_type_ != JoinType::Round)
354
{
355
// almost straight - less than 2.5 degree (#424, #482, #526 & #724)
356
DoMiter(path, j, k, cos_a);
357
}
358
else if (join_type_ == JoinType::Miter)
359
{
360
// miter unless the angle is sufficiently acute to exceed ML
361
if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a);
362
else DoSquare(path, j, k);
363
}
364
else if (join_type_ == JoinType::Round)
365
DoRound(path, j, k, std::atan2(sin_a, cos_a));
366
else if ( join_type_ == JoinType::Bevel)
367
DoBevel(path, j, k);
368
else
369
DoSquare(path, j, k);
370
}
371
372
void ClipperOffset::OffsetPolygon(Group& group, const Path64& path)
373
{
374
path_out.clear();
375
for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j)
376
OffsetPoint(group, path, j, k);
377
solution->emplace_back(path_out);
378
}
379
380
void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
381
{
382
OffsetPolygon(group, path);
383
Path64 reverse_path(path);
384
std::reverse(reverse_path.begin(), reverse_path.end());
385
386
//rebuild normals
387
std::reverse(norms.begin(), norms.end());
388
norms.emplace_back(norms[0]);
389
norms.erase(norms.begin());
390
NegatePath(norms);
391
392
OffsetPolygon(group, reverse_path);
393
}
394
395
void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
396
{
397
// do the line start cap
398
if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0);
399
400
if (std::fabs(group_delta_) <= floating_point_tolerance)
401
path_out.emplace_back(path[0]);
402
else
403
{
404
switch (end_type_)
405
{
406
case EndType::Butt:
407
DoBevel(path, 0, 0);
408
break;
409
case EndType::Round:
410
DoRound(path, 0, 0, PI);
411
break;
412
default:
413
DoSquare(path, 0, 0);
414
break;
415
}
416
}
417
418
size_t highI = path.size() - 1;
419
// offset the left side going forward
420
for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j)
421
OffsetPoint(group, path, j, k);
422
423
// reverse normals
424
for (size_t i = highI; i > 0; --i)
425
norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
426
norms[0] = norms[highI];
427
428
// do the line end cap
429
if (deltaCallback64_)
430
group_delta_ = deltaCallback64_(path, norms, highI, highI);
431
432
if (std::fabs(group_delta_) <= floating_point_tolerance)
433
path_out.emplace_back(path[highI]);
434
else
435
{
436
switch (end_type_)
437
{
438
case EndType::Butt:
439
DoBevel(path, highI, highI);
440
break;
441
case EndType::Round:
442
DoRound(path, highI, highI, PI);
443
break;
444
default:
445
DoSquare(path, highI, highI);
446
break;
447
}
448
}
449
450
for (size_t j = highI -1, k = highI; j > 0; k = j, --j)
451
OffsetPoint(group, path, j, k);
452
solution->emplace_back(path_out);
453
}
454
455
void ClipperOffset::DoGroupOffset(Group& group)
456
{
457
if (group.end_type == EndType::Polygon)
458
{
459
// a straight path (2 points) can now also be 'polygon' offset
460
// where the ends will be treated as (180 deg.) joins
461
if (!group.lowest_path_idx.has_value()) delta_ = std::abs(delta_);
462
group_delta_ = (group.is_reversed) ? -delta_ : delta_;
463
}
464
else
465
group_delta_ = std::abs(delta_);// *0.5;
466
467
double abs_delta = std::fabs(group_delta_);
468
join_type_ = group.join_type;
469
end_type_ = group.end_type;
470
471
if (group.join_type == JoinType::Round || group.end_type == EndType::Round)
472
{
473
// calculate the number of steps required to approximate a circle
474
// (see https://www.angusj.com/clipper2/Docs/Trigonometry.htm)
475
// arcTol - when arc_tolerance_ is undefined (0) then curve imprecision
476
// will be relative to the size of the offset (delta). Obviously very
477
//large offsets will almost always require much less precision.
478
double arcTol = (arc_tolerance_ > floating_point_tolerance) ?
479
std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const;
480
481
double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
482
step_sin_ = std::sin(2 * PI / steps_per_360);
483
step_cos_ = std::cos(2 * PI / steps_per_360);
484
if (group_delta_ < 0.0) step_sin_ = -step_sin_;
485
steps_per_rad_ = steps_per_360 / (2 * PI);
486
}
487
488
//double min_area = PI * Sqr(group_delta_);
489
Paths64::const_iterator path_in_it = group.paths_in.cbegin();
490
for ( ; path_in_it != group.paths_in.cend(); ++path_in_it)
491
{
492
Path64::size_type pathLen = path_in_it->size();
493
path_out.clear();
494
495
if (pathLen == 1) // single point
496
{
497
if (deltaCallback64_)
498
{
499
group_delta_ = deltaCallback64_(*path_in_it, norms, 0, 0);
500
if (group.is_reversed) group_delta_ = -group_delta_;
501
abs_delta = std::fabs(group_delta_);
502
}
503
504
if (group_delta_ < 1) continue;
505
const Point64& pt = (*path_in_it)[0];
506
//single vertex so build a circle or square ...
507
if (group.join_type == JoinType::Round)
508
{
509
double radius = abs_delta;
510
size_t steps = steps_per_rad_ > 0 ? static_cast<size_t>(std::ceil(steps_per_rad_ * 2 * PI)) : 0; //#617
511
path_out = Ellipse(pt, radius, radius, steps);
512
#ifdef USINGZ
513
for (auto& p : path_out) p.z = pt.z;
514
#endif
515
}
516
else
517
{
518
int d = (int)std::ceil(abs_delta);
519
Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d);
520
path_out = r.AsPath();
521
#ifdef USINGZ
522
for (auto& p : path_out) p.z = pt.z;
523
#endif
524
}
525
526
solution->emplace_back(path_out);
527
continue;
528
} // end of offsetting a single point
529
530
if ((pathLen == 2) && (group.end_type == EndType::Joined))
531
end_type_ = (group.join_type == JoinType::Round) ?
532
EndType::Round :
533
EndType::Square;
534
535
BuildNormals(*path_in_it);
536
if (end_type_ == EndType::Polygon) OffsetPolygon(group, *path_in_it);
537
else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, *path_in_it);
538
else OffsetOpenPath(group, *path_in_it);
539
}
540
}
541
542
#ifdef USINGZ
543
void ClipperOffset::ZCB(const Point64& bot1, const Point64& top1,
544
const Point64& bot2, const Point64& top2, Point64& ip)
545
{
546
if (bot1.z && ((bot1.z == bot2.z) || (bot1.z == top2.z))) ip.z = bot1.z;
547
else if (bot2.z && (bot2.z == top1.z)) ip.z = bot2.z;
548
else if (top1.z && (top1.z == top2.z)) ip.z = top1.z;
549
else if (zCallback64_) zCallback64_(bot1, top1, bot2, top2, ip);
550
}
551
#endif
552
553
size_t ClipperOffset::CalcSolutionCapacity()
554
{
555
size_t result = 0;
556
for (const Group& g : groups_)
557
result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size();
558
return result;
559
}
560
561
bool ClipperOffset::CheckReverseOrientation()
562
{
563
// nb: this assumes there's consistency in orientation between groups
564
bool is_reversed_orientation = false;
565
for (const Group& g : groups_)
566
if (g.end_type == EndType::Polygon)
567
{
568
is_reversed_orientation = g.is_reversed;
569
break;
570
}
571
return is_reversed_orientation;
572
}
573
574
void ClipperOffset::ExecuteInternal(double delta)
575
{
576
error_code_ = 0;
577
if (groups_.size() == 0) return;
578
solution->reserve(CalcSolutionCapacity());
579
580
if (std::abs(delta) < 0.5) // ie: offset is insignificant
581
{
582
Paths64::size_type sol_size = 0;
583
for (const Group& group : groups_) sol_size += group.paths_in.size();
584
solution->reserve(sol_size);
585
for (const Group& group : groups_)
586
copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(*solution));
587
}
588
else
589
{
590
591
temp_lim_ = (miter_limit_ <= 1) ?
592
2.0 :
593
2.0 / (miter_limit_ * miter_limit_);
594
595
delta_ = delta;
596
std::vector<Group>::iterator git;
597
for (git = groups_.begin(); git != groups_.end(); ++git)
598
{
599
DoGroupOffset(*git);
600
if (!error_code_) continue; // all OK
601
solution->clear();
602
}
603
}
604
605
if (!solution->size()) return;
606
607
bool paths_reversed = CheckReverseOrientation();
608
//clean up self-intersections ...
609
Clipper64 c;
610
c.PreserveCollinear(preserve_collinear_);
611
//the solution should retain the orientation of the input
612
c.ReverseSolution(reverse_solution_ != paths_reversed);
613
#ifdef USINGZ
614
auto fp = std::bind(&ClipperOffset::ZCB, this, std::placeholders::_1,
615
std::placeholders::_2, std::placeholders::_3,
616
std::placeholders::_4, std::placeholders::_5);
617
c.SetZCallback(fp);
618
#endif
619
c.AddSubject(*solution);
620
if (solution_tree)
621
{
622
if (paths_reversed)
623
c.Execute(ClipType::Union, FillRule::Negative, *solution_tree);
624
else
625
c.Execute(ClipType::Union, FillRule::Positive, *solution_tree);
626
}
627
else
628
{
629
if (paths_reversed)
630
c.Execute(ClipType::Union, FillRule::Negative, *solution);
631
else
632
c.Execute(ClipType::Union, FillRule::Positive, *solution);
633
}
634
}
635
636
void ClipperOffset::Execute(double delta, Paths64& paths64)
637
{
638
paths64.clear();
639
solution = &paths64;
640
solution_tree = nullptr;
641
ExecuteInternal(delta);
642
}
643
644
645
void ClipperOffset::Execute(double delta, PolyTree64& polytree)
646
{
647
polytree.Clear();
648
solution_tree = &polytree;
649
solution = new Paths64();
650
ExecuteInternal(delta);
651
delete solution;
652
solution = nullptr;
653
}
654
655
void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths)
656
{
657
deltaCallback64_ = delta_cb;
658
Execute(1.0, paths);
659
}
660
661
} // namespace
662
663