Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/manifold/src/cross_section/cross_section.cpp
9906 views
1
// Copyright 2023 The Manifold Authors.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include "manifold/cross_section.h"
16
17
#include "../utils.h"
18
#include "clipper2/clipper.core.h"
19
#include "clipper2/clipper.h"
20
#include "clipper2/clipper.offset.h"
21
22
namespace C2 = Clipper2Lib;
23
24
using namespace manifold;
25
26
namespace manifold {
27
struct PathImpl {
28
PathImpl(const C2::PathsD paths_) : paths_(paths_) {}
29
operator const C2::PathsD&() const { return paths_; }
30
const C2::PathsD paths_;
31
};
32
} // namespace manifold
33
34
namespace {
35
const int precision_ = 8;
36
37
C2::ClipType cliptype_of_op(OpType op) {
38
C2::ClipType ct = C2::ClipType::Union;
39
switch (op) {
40
case OpType::Add:
41
break;
42
case OpType::Subtract:
43
ct = C2::ClipType::Difference;
44
break;
45
case OpType::Intersect:
46
ct = C2::ClipType::Intersection;
47
break;
48
};
49
return ct;
50
}
51
52
C2::FillRule fr(CrossSection::FillRule fillrule) {
53
C2::FillRule fr = C2::FillRule::EvenOdd;
54
switch (fillrule) {
55
case CrossSection::FillRule::EvenOdd:
56
break;
57
case CrossSection::FillRule::NonZero:
58
fr = C2::FillRule::NonZero;
59
break;
60
case CrossSection::FillRule::Positive:
61
fr = C2::FillRule::Positive;
62
break;
63
case CrossSection::FillRule::Negative:
64
fr = C2::FillRule::Negative;
65
break;
66
};
67
return fr;
68
}
69
70
C2::JoinType jt(CrossSection::JoinType jointype) {
71
C2::JoinType jt = C2::JoinType::Square;
72
switch (jointype) {
73
case CrossSection::JoinType::Square:
74
break;
75
case CrossSection::JoinType::Round:
76
jt = C2::JoinType::Round;
77
break;
78
case CrossSection::JoinType::Miter:
79
jt = C2::JoinType::Miter;
80
break;
81
case CrossSection::JoinType::Bevel:
82
jt = C2::JoinType::Bevel;
83
break;
84
};
85
return jt;
86
}
87
88
vec2 v2_of_pd(const C2::PointD p) { return {p.x, p.y}; }
89
90
C2::PointD v2_to_pd(const vec2 v) { return C2::PointD(v.x, v.y); }
91
92
C2::PathD pathd_of_contour(const SimplePolygon& ctr) {
93
auto p = C2::PathD();
94
p.reserve(ctr.size());
95
for (auto v : ctr) {
96
p.push_back(v2_to_pd(v));
97
}
98
return p;
99
}
100
101
C2::PathsD transform(const C2::PathsD ps, const mat2x3 m) {
102
const bool invert = la::determinant(mat2(m)) < 0;
103
auto transformed = C2::PathsD();
104
transformed.reserve(ps.size());
105
for (auto path : ps) {
106
auto sz = path.size();
107
auto s = C2::PathD(sz);
108
for (size_t i = 0; i < sz; ++i) {
109
auto idx = invert ? sz - 1 - i : i;
110
s[idx] = v2_to_pd(m * vec3(path[i].x, path[i].y, 1));
111
}
112
transformed.push_back(s);
113
}
114
return transformed;
115
}
116
117
std::shared_ptr<const PathImpl> shared_paths(const C2::PathsD& ps) {
118
return std::make_shared<const PathImpl>(ps);
119
}
120
121
// forward declaration for mutual recursion
122
void decompose_hole(const C2::PolyTreeD* outline,
123
std::vector<C2::PathsD>& polys, C2::PathsD& poly,
124
size_t n_holes, size_t j);
125
126
void decompose_outline(const C2::PolyTreeD* tree,
127
std::vector<C2::PathsD>& polys, size_t i) {
128
auto n_outlines = tree->Count();
129
if (i < n_outlines) {
130
auto outline = tree->Child(i);
131
auto n_holes = outline->Count();
132
auto poly = C2::PathsD(n_holes + 1);
133
poly[0] = outline->Polygon();
134
decompose_hole(outline, polys, poly, n_holes, 0);
135
polys.push_back(poly);
136
if (i < n_outlines - 1) {
137
decompose_outline(tree, polys, i + 1);
138
}
139
}
140
}
141
142
void decompose_hole(const C2::PolyTreeD* outline,
143
std::vector<C2::PathsD>& polys, C2::PathsD& poly,
144
size_t n_holes, size_t j) {
145
if (j < n_holes) {
146
auto child = outline->Child(j);
147
decompose_outline(child, polys, 0);
148
poly[j + 1] = child->Polygon();
149
decompose_hole(outline, polys, poly, n_holes, j + 1);
150
}
151
}
152
153
void flatten(const C2::PolyTreeD* tree, C2::PathsD& polys, size_t i) {
154
auto n_outlines = tree->Count();
155
if (i < n_outlines) {
156
auto outline = tree->Child(i);
157
flatten(outline, polys, 0);
158
polys.push_back(outline->Polygon());
159
if (i < n_outlines - 1) {
160
flatten(tree, polys, i + 1);
161
}
162
}
163
}
164
165
bool V2Lesser(vec2 a, vec2 b) {
166
if (a.x == b.x) return a.y < b.y;
167
return a.x < b.x;
168
}
169
170
void HullBacktrack(const vec2& pt, std::vector<vec2>& stack) {
171
auto sz = stack.size();
172
while (sz >= 2 && CCW(stack[sz - 2], stack[sz - 1], pt, 0.0) <= 0.0) {
173
stack.pop_back();
174
sz = stack.size();
175
}
176
}
177
178
// Based on method described here:
179
// https://www.hackerearth.com/practice/math/geometry/line-sweep-technique/tutorial/
180
// Changed to follow:
181
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
182
// This is the same algorithm (Andrew, also called Montone Chain).
183
C2::PathD HullImpl(SimplePolygon& pts) {
184
size_t len = pts.size();
185
if (len < 3) return C2::PathD(); // not enough points to create a polygon
186
std::sort(pts.begin(), pts.end(), V2Lesser);
187
188
auto lower = std::vector<vec2>{};
189
for (auto& pt : pts) {
190
HullBacktrack(pt, lower);
191
lower.push_back(pt);
192
}
193
auto upper = std::vector<vec2>{};
194
for (auto pt_iter = pts.rbegin(); pt_iter != pts.rend(); pt_iter++) {
195
HullBacktrack(*pt_iter, upper);
196
upper.push_back(*pt_iter);
197
}
198
199
upper.pop_back();
200
lower.pop_back();
201
202
auto path = C2::PathD();
203
path.reserve(lower.size() + upper.size());
204
for (const auto& l : lower) path.push_back(v2_to_pd(l));
205
for (const auto& u : upper) path.push_back(v2_to_pd(u));
206
return path;
207
}
208
} // namespace
209
210
namespace manifold {
211
212
/**
213
* The default constructor is an empty cross-section (containing no contours).
214
*/
215
CrossSection::CrossSection() {
216
paths_ = std::make_shared<const PathImpl>(C2::PathsD());
217
}
218
219
CrossSection::~CrossSection() = default;
220
CrossSection::CrossSection(CrossSection&&) noexcept = default;
221
CrossSection& CrossSection::operator=(CrossSection&&) noexcept = default;
222
223
/**
224
* The copy constructor avoids copying the underlying paths vector (sharing
225
* with its parent via shared_ptr), however subsequent transformations, and
226
* their application will not be shared. It is generally recommended to avoid
227
* this, opting instead to simply create CrossSections with the available
228
* const methods.
229
*/
230
CrossSection::CrossSection(const CrossSection& other) {
231
paths_ = other.paths_;
232
transform_ = other.transform_;
233
}
234
235
CrossSection& CrossSection::operator=(const CrossSection& other) {
236
if (this != &other) {
237
paths_ = other.paths_;
238
transform_ = other.transform_;
239
}
240
return *this;
241
};
242
243
// Private, skips unioning.
244
CrossSection::CrossSection(std::shared_ptr<const PathImpl> ps) { paths_ = ps; }
245
246
/**
247
* Create a 2d cross-section from a single contour. A boolean union operation
248
* (with Positive filling rule by default) is performed to ensure the
249
* resulting CrossSection is free of self-intersections.
250
*
251
* @param contour A closed path outlining the desired cross-section.
252
* @param fillrule The filling rule used to interpret polygon sub-regions
253
* created by self-intersections in contour.
254
*/
255
CrossSection::CrossSection(const SimplePolygon& contour, FillRule fillrule) {
256
auto ps = C2::PathsD{(pathd_of_contour(contour))};
257
paths_ = shared_paths(C2::Union(ps, fr(fillrule), precision_));
258
}
259
260
/**
261
* Create a 2d cross-section from a set of contours (complex polygons). A
262
* boolean union operation (with Positive filling rule by default) is
263
* performed to combine overlapping polygons and ensure the resulting
264
* CrossSection is free of intersections.
265
*
266
* @param contours A set of closed paths describing zero or more complex
267
* polygons.
268
* @param fillrule The filling rule used to interpret polygon sub-regions in
269
* contours.
270
*/
271
CrossSection::CrossSection(const Polygons& contours, FillRule fillrule) {
272
auto ps = C2::PathsD();
273
ps.reserve(contours.size());
274
for (auto ctr : contours) {
275
ps.push_back(pathd_of_contour(ctr));
276
}
277
paths_ = shared_paths(C2::Union(ps, fr(fillrule), precision_));
278
}
279
280
/**
281
* Create a 2d cross-section from an axis-aligned rectangle (bounding box).
282
*
283
* @param rect An axis-aligned rectangular bounding box.
284
*/
285
CrossSection::CrossSection(const Rect& rect) {
286
C2::PathD p(4);
287
p[0] = C2::PointD(rect.min.x, rect.min.y);
288
p[1] = C2::PointD(rect.max.x, rect.min.y);
289
p[2] = C2::PointD(rect.max.x, rect.max.y);
290
p[3] = C2::PointD(rect.min.x, rect.max.y);
291
paths_ = shared_paths(C2::PathsD{p});
292
}
293
294
// Private
295
// All access to paths_ should be done through the GetPaths() method, which
296
// applies the accumulated transform_
297
std::shared_ptr<const PathImpl> CrossSection::GetPaths() const {
298
if (transform_ == mat2x3(la::identity)) {
299
return paths_;
300
}
301
paths_ = shared_paths(::transform(paths_->paths_, transform_));
302
transform_ = mat2x3(la::identity);
303
return paths_;
304
}
305
306
/**
307
* Constructs a square with the given XY dimensions. By default it is
308
* positioned in the first quadrant, touching the origin. If any dimensions in
309
* size are negative, or if all are zero, an empty Manifold will be returned.
310
*
311
* @param size The X, and Y dimensions of the square.
312
* @param center Set to true to shift the center to the origin.
313
*/
314
CrossSection CrossSection::Square(const vec2 size, bool center) {
315
if (size.x < 0.0 || size.y < 0.0 || la::length(size) == 0.0) {
316
return CrossSection();
317
}
318
319
auto p = C2::PathD(4);
320
if (center) {
321
const auto w = size.x / 2;
322
const auto h = size.y / 2;
323
p[0] = C2::PointD(w, h);
324
p[1] = C2::PointD(-w, h);
325
p[2] = C2::PointD(-w, -h);
326
p[3] = C2::PointD(w, -h);
327
} else {
328
const double x = size.x;
329
const double y = size.y;
330
p[0] = C2::PointD(0.0, 0.0);
331
p[1] = C2::PointD(x, 0.0);
332
p[2] = C2::PointD(x, y);
333
p[3] = C2::PointD(0.0, y);
334
}
335
return CrossSection(shared_paths(C2::PathsD{p}));
336
}
337
338
/**
339
* Constructs a circle of a given radius.
340
*
341
* @param radius Radius of the circle. Must be positive.
342
* @param circularSegments Number of segments along its diameter. Default is
343
* calculated by the static Quality defaults according to the radius.
344
*/
345
CrossSection CrossSection::Circle(double radius, int circularSegments) {
346
if (radius <= 0.0) {
347
return CrossSection();
348
}
349
int n = circularSegments > 2 ? circularSegments
350
: Quality::GetCircularSegments(radius);
351
double dPhi = 360.0 / n;
352
auto circle = C2::PathD(n);
353
for (int i = 0; i < n; ++i) {
354
circle[i] = C2::PointD(radius * cosd(dPhi * i), radius * sind(dPhi * i));
355
}
356
return CrossSection(shared_paths(C2::PathsD{circle}));
357
}
358
359
/**
360
* Perform the given boolean operation between this and another CrossSection.
361
*/
362
CrossSection CrossSection::Boolean(const CrossSection& second,
363
OpType op) const {
364
auto ct = cliptype_of_op(op);
365
auto res = C2::BooleanOp(ct, C2::FillRule::Positive, GetPaths()->paths_,
366
second.GetPaths()->paths_, precision_);
367
return CrossSection(shared_paths(res));
368
}
369
370
/**
371
* Perform the given boolean operation on a list of CrossSections. In case of
372
* Subtract, all CrossSections in the tail are differenced from the head.
373
*/
374
CrossSection CrossSection::BatchBoolean(
375
const std::vector<CrossSection>& crossSections, OpType op) {
376
if (crossSections.size() == 0)
377
return CrossSection();
378
else if (crossSections.size() == 1)
379
return crossSections[0];
380
381
auto subjs = crossSections[0].GetPaths();
382
383
if (op == OpType::Intersect) {
384
auto res = subjs->paths_;
385
for (size_t i = 1; i < crossSections.size(); ++i) {
386
res = C2::BooleanOp(C2::ClipType::Intersection, C2::FillRule::Positive,
387
res, crossSections[i].GetPaths()->paths_, precision_);
388
}
389
return CrossSection(shared_paths(res));
390
}
391
392
int n_clips = 0;
393
for (size_t i = 1; i < crossSections.size(); ++i) {
394
n_clips += crossSections[i].GetPaths()->paths_.size();
395
}
396
auto clips = C2::PathsD();
397
clips.reserve(n_clips);
398
for (size_t i = 1; i < crossSections.size(); ++i) {
399
auto ps = crossSections[i].GetPaths();
400
clips.insert(clips.end(), ps->paths_.begin(), ps->paths_.end());
401
}
402
403
auto ct = cliptype_of_op(op);
404
auto res = C2::BooleanOp(ct, C2::FillRule::Positive, subjs->paths_, clips,
405
precision_);
406
return CrossSection(shared_paths(res));
407
}
408
409
/**
410
* Compute the boolean union between two cross-sections.
411
*/
412
CrossSection CrossSection::operator+(const CrossSection& Q) const {
413
return Boolean(Q, OpType::Add);
414
}
415
416
/**
417
* Compute the boolean union between two cross-sections, assigning the result
418
* to the first.
419
*/
420
CrossSection& CrossSection::operator+=(const CrossSection& Q) {
421
*this = *this + Q;
422
return *this;
423
}
424
425
/**
426
* Compute the boolean difference of a (clip) cross-section from another
427
* (subject).
428
*/
429
CrossSection CrossSection::operator-(const CrossSection& Q) const {
430
return Boolean(Q, OpType::Subtract);
431
}
432
433
/**
434
* Compute the boolean difference of a (clip) cross-section from a another
435
* (subject), assigning the result to the subject.
436
*/
437
CrossSection& CrossSection::operator-=(const CrossSection& Q) {
438
*this = *this - Q;
439
return *this;
440
}
441
442
/**
443
* Compute the boolean intersection between two cross-sections.
444
*/
445
CrossSection CrossSection::operator^(const CrossSection& Q) const {
446
return Boolean(Q, OpType::Intersect);
447
}
448
449
/**
450
* Compute the boolean intersection between two cross-sections, assigning the
451
* result to the first.
452
*/
453
CrossSection& CrossSection::operator^=(const CrossSection& Q) {
454
*this = *this ^ Q;
455
return *this;
456
}
457
458
/**
459
* Construct a CrossSection from a vector of other CrossSections (batch
460
* boolean union).
461
*/
462
CrossSection CrossSection::Compose(
463
const std::vector<CrossSection>& crossSections) {
464
return BatchBoolean(crossSections, OpType::Add);
465
}
466
467
/**
468
* This operation returns a vector of CrossSections that are topologically
469
* disconnected, each containing one outline contour with zero or more
470
* holes.
471
*/
472
std::vector<CrossSection> CrossSection::Decompose() const {
473
if (NumContour() < 2) {
474
return std::vector<CrossSection>{CrossSection(*this)};
475
}
476
477
C2::PolyTreeD tree;
478
C2::BooleanOp(C2::ClipType::Union, C2::FillRule::Positive, GetPaths()->paths_,
479
C2::PathsD(), tree, precision_);
480
481
auto polys = std::vector<C2::PathsD>();
482
decompose_outline(&tree, polys, 0);
483
484
auto comps = std::vector<CrossSection>();
485
comps.reserve(polys.size());
486
// reverse the stack while wrapping
487
for (auto poly = polys.rbegin(); poly != polys.rend(); ++poly)
488
comps.emplace_back(CrossSection(shared_paths(*poly)));
489
490
return comps;
491
}
492
493
/**
494
* Move this CrossSection in space. This operation can be chained. Transforms
495
* are combined and applied lazily.
496
*
497
* @param v The vector to add to every vertex.
498
*/
499
CrossSection CrossSection::Translate(const vec2 v) const {
500
mat2x3 m({1.0, 0.0}, //
501
{0.0, 1.0}, //
502
{v.x, v.y});
503
return Transform(m);
504
}
505
506
/**
507
* Applies a (Z-axis) rotation to the CrossSection, in degrees. This operation
508
* can be chained. Transforms are combined and applied lazily.
509
*
510
* @param degrees degrees about the Z-axis to rotate.
511
*/
512
CrossSection CrossSection::Rotate(double degrees) const {
513
auto s = sind(degrees);
514
auto c = cosd(degrees);
515
mat2x3 m({c, s}, //
516
{-s, c}, //
517
{0.0, 0.0});
518
return Transform(m);
519
}
520
521
/**
522
* Scale this CrossSection in space. This operation can be chained. Transforms
523
* are combined and applied lazily.
524
*
525
* @param scale The vector to multiply every vertex by per component.
526
*/
527
CrossSection CrossSection::Scale(const vec2 scale) const {
528
mat2x3 m({scale.x, 0.0}, //
529
{0.0, scale.y}, //
530
{0.0, 0.0});
531
return Transform(m);
532
}
533
534
/**
535
* Mirror this CrossSection over the arbitrary axis whose normal is described by
536
* the unit form of the given vector. If the length of the vector is zero, an
537
* empty CrossSection is returned. This operation can be chained. Transforms are
538
* combined and applied lazily.
539
*
540
* @param ax the axis to be mirrored over
541
*/
542
CrossSection CrossSection::Mirror(const vec2 ax) const {
543
if (la::length(ax) == 0.) {
544
return CrossSection();
545
}
546
auto n = la::normalize(ax);
547
auto m = mat2x3(mat2(la::identity) - 2.0 * la::outerprod(n, n), vec2(0.0));
548
return Transform(m);
549
}
550
551
/**
552
* Transform this CrossSection in space. The first two columns form a 2x2
553
* matrix transform and the last is a translation vector. This operation can
554
* be chained. Transforms are combined and applied lazily.
555
*
556
* @param m The affine transform matrix to apply to all the vertices.
557
*/
558
CrossSection CrossSection::Transform(const mat2x3& m) const {
559
auto transformed = CrossSection();
560
transformed.transform_ = m * Mat3(transform_);
561
transformed.paths_ = paths_;
562
return transformed;
563
}
564
565
/**
566
* Move the vertices of this CrossSection (creating a new one) according to
567
* any arbitrary input function, followed by a union operation (with a
568
* Positive fill rule) that ensures any introduced intersections are not
569
* included in the result.
570
*
571
* @param warpFunc A function that modifies a given vertex position.
572
*/
573
CrossSection CrossSection::Warp(std::function<void(vec2&)> warpFunc) const {
574
return WarpBatch([&warpFunc](VecView<vec2> vecs) {
575
for (vec2& p : vecs) {
576
warpFunc(p);
577
}
578
});
579
}
580
581
/**
582
* Same as CrossSection::Warp but calls warpFunc with
583
* a VecView which is roughly equivalent to std::span
584
* pointing to all vec2 elements to be modified in-place
585
*
586
* @param warpFunc A function that modifies multiple vertex positions.
587
*/
588
CrossSection CrossSection::WarpBatch(
589
std::function<void(VecView<vec2>)> warpFunc) const {
590
std::vector<vec2> tmp_verts;
591
C2::PathsD paths = GetPaths()->paths_; // deep copy
592
for (C2::PathD const& path : paths) {
593
for (C2::PointD const& p : path) {
594
tmp_verts.push_back(v2_of_pd(p));
595
}
596
}
597
598
warpFunc(VecView<vec2>(tmp_verts.data(), tmp_verts.size()));
599
600
auto cursor = tmp_verts.begin();
601
for (C2::PathD& path : paths) {
602
for (C2::PointD& p : path) {
603
p = v2_to_pd(*cursor);
604
++cursor;
605
}
606
}
607
608
return CrossSection(
609
shared_paths(C2::Union(paths, C2::FillRule::Positive, precision_)));
610
}
611
612
/**
613
* Remove vertices from the contours in this CrossSection that are less than
614
* the specified distance epsilon from an imaginary line that passes through
615
* its two adjacent vertices. Near duplicate vertices and collinear points
616
* will be removed at lower epsilons, with elimination of line segments
617
* becoming increasingly aggressive with larger epsilons.
618
*
619
* It is recommended to apply this function following Offset, in order to
620
* clean up any spurious tiny line segments introduced that do not improve
621
* quality in any meaningful way. This is particularly important if further
622
* offseting operations are to be performed, which would compound the issue.
623
*/
624
CrossSection CrossSection::Simplify(double epsilon) const {
625
C2::PolyTreeD tree;
626
C2::BooleanOp(C2::ClipType::Union, C2::FillRule::Positive, GetPaths()->paths_,
627
C2::PathsD(), tree, precision_);
628
629
C2::PathsD polys;
630
flatten(&tree, polys, 0);
631
632
// Filter out contours less than epsilon wide.
633
C2::PathsD filtered;
634
for (C2::PathD poly : polys) {
635
auto area = C2::Area(poly);
636
Rect box;
637
for (auto vert : poly) {
638
box.Union(vec2(vert.x, vert.y));
639
}
640
vec2 size = box.Size();
641
if (std::abs(area) > std::max(size.x, size.y) * epsilon) {
642
filtered.push_back(poly);
643
}
644
}
645
646
auto ps = SimplifyPaths(filtered, epsilon, true);
647
return CrossSection(shared_paths(ps));
648
}
649
650
/**
651
* Inflate the contours in CrossSection by the specified delta, handling
652
* corners according to the given JoinType.
653
*
654
* @param delta Positive deltas will cause the expansion of outlining contours
655
* to expand, and retraction of inner (hole) contours. Negative deltas will
656
* have the opposite effect.
657
* @param jointype The join type specifying the treatment of contour joins
658
* (corners). Defaults to Round.
659
* @param miter_limit The maximum distance in multiples of delta that vertices
660
* can be offset from their original positions with before squaring is
661
* applied, <B>when the join type is Miter</B> (default is 2, which is the
662
* minimum allowed). See the [Clipper2
663
* MiterLimit](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/Classes/ClipperOffset/Properties/MiterLimit.htm)
664
* page for a visual example.
665
* @param circularSegments Number of segments per 360 degrees of
666
* <B>JoinType::Round</B> corners (roughly, the number of vertices that
667
* will be added to each contour). Default is calculated by the static Quality
668
* defaults according to the radius.
669
*/
670
CrossSection CrossSection::Offset(double delta, JoinType jointype,
671
double miter_limit,
672
int circularSegments) const {
673
double arc_tol = 0.;
674
if (jointype == JoinType::Round) {
675
int n = circularSegments > 2 ? circularSegments
676
: Quality::GetCircularSegments(delta);
677
// This calculates tolerance as a function of circular segments and delta
678
// (radius) in order to get back the same number of segments in Clipper2:
679
// steps_per_360 = PI / acos(1 - arc_tol / abs_delta)
680
const double abs_delta = std::fabs(delta);
681
arc_tol = (std::cos(Clipper2Lib::PI / n) - 1) * -abs_delta;
682
}
683
auto ps =
684
C2::InflatePaths(GetPaths()->paths_, delta, jt(jointype),
685
C2::EndType::Polygon, miter_limit, precision_, arc_tol);
686
return CrossSection(shared_paths(ps));
687
}
688
689
/**
690
* Compute the convex hull enveloping a set of cross-sections.
691
*
692
* @param crossSections A vector of cross-sections over which to compute a
693
* convex hull.
694
*/
695
CrossSection CrossSection::Hull(
696
const std::vector<CrossSection>& crossSections) {
697
int n = 0;
698
for (auto cs : crossSections) n += cs.NumVert();
699
SimplePolygon pts;
700
pts.reserve(n);
701
for (auto cs : crossSections) {
702
auto paths = cs.GetPaths()->paths_;
703
for (auto path : paths) {
704
for (auto p : path) {
705
pts.push_back(v2_of_pd(p));
706
}
707
}
708
}
709
return CrossSection(shared_paths(C2::PathsD{HullImpl(pts)}));
710
}
711
712
/**
713
* Compute the convex hull of this cross-section.
714
*/
715
CrossSection CrossSection::Hull() const {
716
return Hull(std::vector<CrossSection>{*this});
717
}
718
719
/**
720
* Compute the convex hull of a set of points. If the given points are fewer
721
* than 3, an empty CrossSection will be returned.
722
*
723
* @param pts A vector of 2-dimensional points over which to compute a convex
724
* hull.
725
*/
726
CrossSection CrossSection::Hull(SimplePolygon pts) {
727
return CrossSection(shared_paths(C2::PathsD{HullImpl(pts)}));
728
}
729
730
/**
731
* Compute the convex hull of a set of points/polygons. If the given points are
732
* fewer than 3, an empty CrossSection will be returned.
733
*
734
* @param polys A vector of vectors of 2-dimensional points over which to
735
* compute a convex hull.
736
*/
737
CrossSection CrossSection::Hull(const Polygons polys) {
738
SimplePolygon pts;
739
for (auto poly : polys) {
740
for (auto p : poly) {
741
pts.push_back(p);
742
}
743
}
744
return Hull(pts);
745
}
746
747
/**
748
* Return the total area covered by complex polygons making up the
749
* CrossSection.
750
*/
751
double CrossSection::Area() const { return C2::Area(GetPaths()->paths_); }
752
753
/**
754
* Return the number of vertices in the CrossSection.
755
*/
756
size_t CrossSection::NumVert() const {
757
size_t n = 0;
758
auto paths = GetPaths()->paths_;
759
for (auto p : paths) {
760
n += p.size();
761
}
762
return n;
763
}
764
765
/**
766
* Return the number of contours (both outer and inner paths) in the
767
* CrossSection.
768
*/
769
size_t CrossSection::NumContour() const { return GetPaths()->paths_.size(); }
770
771
/**
772
* Does the CrossSection contain any contours?
773
*/
774
bool CrossSection::IsEmpty() const { return GetPaths()->paths_.empty(); }
775
776
/**
777
* Returns the axis-aligned bounding rectangle of all the CrossSections'
778
* vertices.
779
*/
780
Rect CrossSection::Bounds() const {
781
auto r = C2::GetBounds(GetPaths()->paths_);
782
return Rect({r.left, r.bottom}, {r.right, r.top});
783
}
784
785
/**
786
* Return the contours of this CrossSection as a Polygons.
787
*/
788
Polygons CrossSection::ToPolygons() const {
789
auto polys = Polygons();
790
auto paths = GetPaths()->paths_;
791
polys.reserve(paths.size());
792
for (auto p : paths) {
793
auto sp = SimplePolygon();
794
sp.reserve(p.size());
795
for (auto v : p) {
796
sp.push_back({v.x, v.y});
797
}
798
polys.push_back(sp);
799
}
800
return polys;
801
}
802
} // namespace manifold
803
804