cross_section.cpp 24 KB

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