Path: blob/master/thirdparty/manifold/src/cross_section/cross_section.cpp
9906 views
// Copyright 2023 The Manifold Authors.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314#include "manifold/cross_section.h"1516#include "../utils.h"17#include "clipper2/clipper.core.h"18#include "clipper2/clipper.h"19#include "clipper2/clipper.offset.h"2021namespace C2 = Clipper2Lib;2223using namespace manifold;2425namespace manifold {26struct PathImpl {27PathImpl(const C2::PathsD paths_) : paths_(paths_) {}28operator const C2::PathsD&() const { return paths_; }29const C2::PathsD paths_;30};31} // namespace manifold3233namespace {34const int precision_ = 8;3536C2::ClipType cliptype_of_op(OpType op) {37C2::ClipType ct = C2::ClipType::Union;38switch (op) {39case OpType::Add:40break;41case OpType::Subtract:42ct = C2::ClipType::Difference;43break;44case OpType::Intersect:45ct = C2::ClipType::Intersection;46break;47};48return ct;49}5051C2::FillRule fr(CrossSection::FillRule fillrule) {52C2::FillRule fr = C2::FillRule::EvenOdd;53switch (fillrule) {54case CrossSection::FillRule::EvenOdd:55break;56case CrossSection::FillRule::NonZero:57fr = C2::FillRule::NonZero;58break;59case CrossSection::FillRule::Positive:60fr = C2::FillRule::Positive;61break;62case CrossSection::FillRule::Negative:63fr = C2::FillRule::Negative;64break;65};66return fr;67}6869C2::JoinType jt(CrossSection::JoinType jointype) {70C2::JoinType jt = C2::JoinType::Square;71switch (jointype) {72case CrossSection::JoinType::Square:73break;74case CrossSection::JoinType::Round:75jt = C2::JoinType::Round;76break;77case CrossSection::JoinType::Miter:78jt = C2::JoinType::Miter;79break;80case CrossSection::JoinType::Bevel:81jt = C2::JoinType::Bevel;82break;83};84return jt;85}8687vec2 v2_of_pd(const C2::PointD p) { return {p.x, p.y}; }8889C2::PointD v2_to_pd(const vec2 v) { return C2::PointD(v.x, v.y); }9091C2::PathD pathd_of_contour(const SimplePolygon& ctr) {92auto p = C2::PathD();93p.reserve(ctr.size());94for (auto v : ctr) {95p.push_back(v2_to_pd(v));96}97return p;98}99100C2::PathsD transform(const C2::PathsD ps, const mat2x3 m) {101const bool invert = la::determinant(mat2(m)) < 0;102auto transformed = C2::PathsD();103transformed.reserve(ps.size());104for (auto path : ps) {105auto sz = path.size();106auto s = C2::PathD(sz);107for (size_t i = 0; i < sz; ++i) {108auto idx = invert ? sz - 1 - i : i;109s[idx] = v2_to_pd(m * vec3(path[i].x, path[i].y, 1));110}111transformed.push_back(s);112}113return transformed;114}115116std::shared_ptr<const PathImpl> shared_paths(const C2::PathsD& ps) {117return std::make_shared<const PathImpl>(ps);118}119120// forward declaration for mutual recursion121void decompose_hole(const C2::PolyTreeD* outline,122std::vector<C2::PathsD>& polys, C2::PathsD& poly,123size_t n_holes, size_t j);124125void decompose_outline(const C2::PolyTreeD* tree,126std::vector<C2::PathsD>& polys, size_t i) {127auto n_outlines = tree->Count();128if (i < n_outlines) {129auto outline = tree->Child(i);130auto n_holes = outline->Count();131auto poly = C2::PathsD(n_holes + 1);132poly[0] = outline->Polygon();133decompose_hole(outline, polys, poly, n_holes, 0);134polys.push_back(poly);135if (i < n_outlines - 1) {136decompose_outline(tree, polys, i + 1);137}138}139}140141void decompose_hole(const C2::PolyTreeD* outline,142std::vector<C2::PathsD>& polys, C2::PathsD& poly,143size_t n_holes, size_t j) {144if (j < n_holes) {145auto child = outline->Child(j);146decompose_outline(child, polys, 0);147poly[j + 1] = child->Polygon();148decompose_hole(outline, polys, poly, n_holes, j + 1);149}150}151152void flatten(const C2::PolyTreeD* tree, C2::PathsD& polys, size_t i) {153auto n_outlines = tree->Count();154if (i < n_outlines) {155auto outline = tree->Child(i);156flatten(outline, polys, 0);157polys.push_back(outline->Polygon());158if (i < n_outlines - 1) {159flatten(tree, polys, i + 1);160}161}162}163164bool V2Lesser(vec2 a, vec2 b) {165if (a.x == b.x) return a.y < b.y;166return a.x < b.x;167}168169void HullBacktrack(const vec2& pt, std::vector<vec2>& stack) {170auto sz = stack.size();171while (sz >= 2 && CCW(stack[sz - 2], stack[sz - 1], pt, 0.0) <= 0.0) {172stack.pop_back();173sz = stack.size();174}175}176177// Based on method described here:178// https://www.hackerearth.com/practice/math/geometry/line-sweep-technique/tutorial/179// Changed to follow:180// https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain181// This is the same algorithm (Andrew, also called Montone Chain).182C2::PathD HullImpl(SimplePolygon& pts) {183size_t len = pts.size();184if (len < 3) return C2::PathD(); // not enough points to create a polygon185std::sort(pts.begin(), pts.end(), V2Lesser);186187auto lower = std::vector<vec2>{};188for (auto& pt : pts) {189HullBacktrack(pt, lower);190lower.push_back(pt);191}192auto upper = std::vector<vec2>{};193for (auto pt_iter = pts.rbegin(); pt_iter != pts.rend(); pt_iter++) {194HullBacktrack(*pt_iter, upper);195upper.push_back(*pt_iter);196}197198upper.pop_back();199lower.pop_back();200201auto path = C2::PathD();202path.reserve(lower.size() + upper.size());203for (const auto& l : lower) path.push_back(v2_to_pd(l));204for (const auto& u : upper) path.push_back(v2_to_pd(u));205return path;206}207} // namespace208209namespace manifold {210211/**212* The default constructor is an empty cross-section (containing no contours).213*/214CrossSection::CrossSection() {215paths_ = std::make_shared<const PathImpl>(C2::PathsD());216}217218CrossSection::~CrossSection() = default;219CrossSection::CrossSection(CrossSection&&) noexcept = default;220CrossSection& CrossSection::operator=(CrossSection&&) noexcept = default;221222/**223* The copy constructor avoids copying the underlying paths vector (sharing224* with its parent via shared_ptr), however subsequent transformations, and225* their application will not be shared. It is generally recommended to avoid226* this, opting instead to simply create CrossSections with the available227* const methods.228*/229CrossSection::CrossSection(const CrossSection& other) {230paths_ = other.paths_;231transform_ = other.transform_;232}233234CrossSection& CrossSection::operator=(const CrossSection& other) {235if (this != &other) {236paths_ = other.paths_;237transform_ = other.transform_;238}239return *this;240};241242// Private, skips unioning.243CrossSection::CrossSection(std::shared_ptr<const PathImpl> ps) { paths_ = ps; }244245/**246* Create a 2d cross-section from a single contour. A boolean union operation247* (with Positive filling rule by default) is performed to ensure the248* resulting CrossSection is free of self-intersections.249*250* @param contour A closed path outlining the desired cross-section.251* @param fillrule The filling rule used to interpret polygon sub-regions252* created by self-intersections in contour.253*/254CrossSection::CrossSection(const SimplePolygon& contour, FillRule fillrule) {255auto ps = C2::PathsD{(pathd_of_contour(contour))};256paths_ = shared_paths(C2::Union(ps, fr(fillrule), precision_));257}258259/**260* Create a 2d cross-section from a set of contours (complex polygons). A261* boolean union operation (with Positive filling rule by default) is262* performed to combine overlapping polygons and ensure the resulting263* CrossSection is free of intersections.264*265* @param contours A set of closed paths describing zero or more complex266* polygons.267* @param fillrule The filling rule used to interpret polygon sub-regions in268* contours.269*/270CrossSection::CrossSection(const Polygons& contours, FillRule fillrule) {271auto ps = C2::PathsD();272ps.reserve(contours.size());273for (auto ctr : contours) {274ps.push_back(pathd_of_contour(ctr));275}276paths_ = shared_paths(C2::Union(ps, fr(fillrule), precision_));277}278279/**280* Create a 2d cross-section from an axis-aligned rectangle (bounding box).281*282* @param rect An axis-aligned rectangular bounding box.283*/284CrossSection::CrossSection(const Rect& rect) {285C2::PathD p(4);286p[0] = C2::PointD(rect.min.x, rect.min.y);287p[1] = C2::PointD(rect.max.x, rect.min.y);288p[2] = C2::PointD(rect.max.x, rect.max.y);289p[3] = C2::PointD(rect.min.x, rect.max.y);290paths_ = shared_paths(C2::PathsD{p});291}292293// Private294// All access to paths_ should be done through the GetPaths() method, which295// applies the accumulated transform_296std::shared_ptr<const PathImpl> CrossSection::GetPaths() const {297if (transform_ == mat2x3(la::identity)) {298return paths_;299}300paths_ = shared_paths(::transform(paths_->paths_, transform_));301transform_ = mat2x3(la::identity);302return paths_;303}304305/**306* Constructs a square with the given XY dimensions. By default it is307* positioned in the first quadrant, touching the origin. If any dimensions in308* size are negative, or if all are zero, an empty Manifold will be returned.309*310* @param size The X, and Y dimensions of the square.311* @param center Set to true to shift the center to the origin.312*/313CrossSection CrossSection::Square(const vec2 size, bool center) {314if (size.x < 0.0 || size.y < 0.0 || la::length(size) == 0.0) {315return CrossSection();316}317318auto p = C2::PathD(4);319if (center) {320const auto w = size.x / 2;321const auto h = size.y / 2;322p[0] = C2::PointD(w, h);323p[1] = C2::PointD(-w, h);324p[2] = C2::PointD(-w, -h);325p[3] = C2::PointD(w, -h);326} else {327const double x = size.x;328const double y = size.y;329p[0] = C2::PointD(0.0, 0.0);330p[1] = C2::PointD(x, 0.0);331p[2] = C2::PointD(x, y);332p[3] = C2::PointD(0.0, y);333}334return CrossSection(shared_paths(C2::PathsD{p}));335}336337/**338* Constructs a circle of a given radius.339*340* @param radius Radius of the circle. Must be positive.341* @param circularSegments Number of segments along its diameter. Default is342* calculated by the static Quality defaults according to the radius.343*/344CrossSection CrossSection::Circle(double radius, int circularSegments) {345if (radius <= 0.0) {346return CrossSection();347}348int n = circularSegments > 2 ? circularSegments349: Quality::GetCircularSegments(radius);350double dPhi = 360.0 / n;351auto circle = C2::PathD(n);352for (int i = 0; i < n; ++i) {353circle[i] = C2::PointD(radius * cosd(dPhi * i), radius * sind(dPhi * i));354}355return CrossSection(shared_paths(C2::PathsD{circle}));356}357358/**359* Perform the given boolean operation between this and another CrossSection.360*/361CrossSection CrossSection::Boolean(const CrossSection& second,362OpType op) const {363auto ct = cliptype_of_op(op);364auto res = C2::BooleanOp(ct, C2::FillRule::Positive, GetPaths()->paths_,365second.GetPaths()->paths_, precision_);366return CrossSection(shared_paths(res));367}368369/**370* Perform the given boolean operation on a list of CrossSections. In case of371* Subtract, all CrossSections in the tail are differenced from the head.372*/373CrossSection CrossSection::BatchBoolean(374const std::vector<CrossSection>& crossSections, OpType op) {375if (crossSections.size() == 0)376return CrossSection();377else if (crossSections.size() == 1)378return crossSections[0];379380auto subjs = crossSections[0].GetPaths();381382if (op == OpType::Intersect) {383auto res = subjs->paths_;384for (size_t i = 1; i < crossSections.size(); ++i) {385res = C2::BooleanOp(C2::ClipType::Intersection, C2::FillRule::Positive,386res, crossSections[i].GetPaths()->paths_, precision_);387}388return CrossSection(shared_paths(res));389}390391int n_clips = 0;392for (size_t i = 1; i < crossSections.size(); ++i) {393n_clips += crossSections[i].GetPaths()->paths_.size();394}395auto clips = C2::PathsD();396clips.reserve(n_clips);397for (size_t i = 1; i < crossSections.size(); ++i) {398auto ps = crossSections[i].GetPaths();399clips.insert(clips.end(), ps->paths_.begin(), ps->paths_.end());400}401402auto ct = cliptype_of_op(op);403auto res = C2::BooleanOp(ct, C2::FillRule::Positive, subjs->paths_, clips,404precision_);405return CrossSection(shared_paths(res));406}407408/**409* Compute the boolean union between two cross-sections.410*/411CrossSection CrossSection::operator+(const CrossSection& Q) const {412return Boolean(Q, OpType::Add);413}414415/**416* Compute the boolean union between two cross-sections, assigning the result417* to the first.418*/419CrossSection& CrossSection::operator+=(const CrossSection& Q) {420*this = *this + Q;421return *this;422}423424/**425* Compute the boolean difference of a (clip) cross-section from another426* (subject).427*/428CrossSection CrossSection::operator-(const CrossSection& Q) const {429return Boolean(Q, OpType::Subtract);430}431432/**433* Compute the boolean difference of a (clip) cross-section from a another434* (subject), assigning the result to the subject.435*/436CrossSection& CrossSection::operator-=(const CrossSection& Q) {437*this = *this - Q;438return *this;439}440441/**442* Compute the boolean intersection between two cross-sections.443*/444CrossSection CrossSection::operator^(const CrossSection& Q) const {445return Boolean(Q, OpType::Intersect);446}447448/**449* Compute the boolean intersection between two cross-sections, assigning the450* result to the first.451*/452CrossSection& CrossSection::operator^=(const CrossSection& Q) {453*this = *this ^ Q;454return *this;455}456457/**458* Construct a CrossSection from a vector of other CrossSections (batch459* boolean union).460*/461CrossSection CrossSection::Compose(462const std::vector<CrossSection>& crossSections) {463return BatchBoolean(crossSections, OpType::Add);464}465466/**467* This operation returns a vector of CrossSections that are topologically468* disconnected, each containing one outline contour with zero or more469* holes.470*/471std::vector<CrossSection> CrossSection::Decompose() const {472if (NumContour() < 2) {473return std::vector<CrossSection>{CrossSection(*this)};474}475476C2::PolyTreeD tree;477C2::BooleanOp(C2::ClipType::Union, C2::FillRule::Positive, GetPaths()->paths_,478C2::PathsD(), tree, precision_);479480auto polys = std::vector<C2::PathsD>();481decompose_outline(&tree, polys, 0);482483auto comps = std::vector<CrossSection>();484comps.reserve(polys.size());485// reverse the stack while wrapping486for (auto poly = polys.rbegin(); poly != polys.rend(); ++poly)487comps.emplace_back(CrossSection(shared_paths(*poly)));488489return comps;490}491492/**493* Move this CrossSection in space. This operation can be chained. Transforms494* are combined and applied lazily.495*496* @param v The vector to add to every vertex.497*/498CrossSection CrossSection::Translate(const vec2 v) const {499mat2x3 m({1.0, 0.0}, //500{0.0, 1.0}, //501{v.x, v.y});502return Transform(m);503}504505/**506* Applies a (Z-axis) rotation to the CrossSection, in degrees. This operation507* can be chained. Transforms are combined and applied lazily.508*509* @param degrees degrees about the Z-axis to rotate.510*/511CrossSection CrossSection::Rotate(double degrees) const {512auto s = sind(degrees);513auto c = cosd(degrees);514mat2x3 m({c, s}, //515{-s, c}, //516{0.0, 0.0});517return Transform(m);518}519520/**521* Scale this CrossSection in space. This operation can be chained. Transforms522* are combined and applied lazily.523*524* @param scale The vector to multiply every vertex by per component.525*/526CrossSection CrossSection::Scale(const vec2 scale) const {527mat2x3 m({scale.x, 0.0}, //528{0.0, scale.y}, //529{0.0, 0.0});530return Transform(m);531}532533/**534* Mirror this CrossSection over the arbitrary axis whose normal is described by535* the unit form of the given vector. If the length of the vector is zero, an536* empty CrossSection is returned. This operation can be chained. Transforms are537* combined and applied lazily.538*539* @param ax the axis to be mirrored over540*/541CrossSection CrossSection::Mirror(const vec2 ax) const {542if (la::length(ax) == 0.) {543return CrossSection();544}545auto n = la::normalize(ax);546auto m = mat2x3(mat2(la::identity) - 2.0 * la::outerprod(n, n), vec2(0.0));547return Transform(m);548}549550/**551* Transform this CrossSection in space. The first two columns form a 2x2552* matrix transform and the last is a translation vector. This operation can553* be chained. Transforms are combined and applied lazily.554*555* @param m The affine transform matrix to apply to all the vertices.556*/557CrossSection CrossSection::Transform(const mat2x3& m) const {558auto transformed = CrossSection();559transformed.transform_ = m * Mat3(transform_);560transformed.paths_ = paths_;561return transformed;562}563564/**565* Move the vertices of this CrossSection (creating a new one) according to566* any arbitrary input function, followed by a union operation (with a567* Positive fill rule) that ensures any introduced intersections are not568* included in the result.569*570* @param warpFunc A function that modifies a given vertex position.571*/572CrossSection CrossSection::Warp(std::function<void(vec2&)> warpFunc) const {573return WarpBatch([&warpFunc](VecView<vec2> vecs) {574for (vec2& p : vecs) {575warpFunc(p);576}577});578}579580/**581* Same as CrossSection::Warp but calls warpFunc with582* a VecView which is roughly equivalent to std::span583* pointing to all vec2 elements to be modified in-place584*585* @param warpFunc A function that modifies multiple vertex positions.586*/587CrossSection CrossSection::WarpBatch(588std::function<void(VecView<vec2>)> warpFunc) const {589std::vector<vec2> tmp_verts;590C2::PathsD paths = GetPaths()->paths_; // deep copy591for (C2::PathD const& path : paths) {592for (C2::PointD const& p : path) {593tmp_verts.push_back(v2_of_pd(p));594}595}596597warpFunc(VecView<vec2>(tmp_verts.data(), tmp_verts.size()));598599auto cursor = tmp_verts.begin();600for (C2::PathD& path : paths) {601for (C2::PointD& p : path) {602p = v2_to_pd(*cursor);603++cursor;604}605}606607return CrossSection(608shared_paths(C2::Union(paths, C2::FillRule::Positive, precision_)));609}610611/**612* Remove vertices from the contours in this CrossSection that are less than613* the specified distance epsilon from an imaginary line that passes through614* its two adjacent vertices. Near duplicate vertices and collinear points615* will be removed at lower epsilons, with elimination of line segments616* becoming increasingly aggressive with larger epsilons.617*618* It is recommended to apply this function following Offset, in order to619* clean up any spurious tiny line segments introduced that do not improve620* quality in any meaningful way. This is particularly important if further621* offseting operations are to be performed, which would compound the issue.622*/623CrossSection CrossSection::Simplify(double epsilon) const {624C2::PolyTreeD tree;625C2::BooleanOp(C2::ClipType::Union, C2::FillRule::Positive, GetPaths()->paths_,626C2::PathsD(), tree, precision_);627628C2::PathsD polys;629flatten(&tree, polys, 0);630631// Filter out contours less than epsilon wide.632C2::PathsD filtered;633for (C2::PathD poly : polys) {634auto area = C2::Area(poly);635Rect box;636for (auto vert : poly) {637box.Union(vec2(vert.x, vert.y));638}639vec2 size = box.Size();640if (std::abs(area) > std::max(size.x, size.y) * epsilon) {641filtered.push_back(poly);642}643}644645auto ps = SimplifyPaths(filtered, epsilon, true);646return CrossSection(shared_paths(ps));647}648649/**650* Inflate the contours in CrossSection by the specified delta, handling651* corners according to the given JoinType.652*653* @param delta Positive deltas will cause the expansion of outlining contours654* to expand, and retraction of inner (hole) contours. Negative deltas will655* have the opposite effect.656* @param jointype The join type specifying the treatment of contour joins657* (corners). Defaults to Round.658* @param miter_limit The maximum distance in multiples of delta that vertices659* can be offset from their original positions with before squaring is660* applied, <B>when the join type is Miter</B> (default is 2, which is the661* minimum allowed). See the [Clipper2662* MiterLimit](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/Classes/ClipperOffset/Properties/MiterLimit.htm)663* page for a visual example.664* @param circularSegments Number of segments per 360 degrees of665* <B>JoinType::Round</B> corners (roughly, the number of vertices that666* will be added to each contour). Default is calculated by the static Quality667* defaults according to the radius.668*/669CrossSection CrossSection::Offset(double delta, JoinType jointype,670double miter_limit,671int circularSegments) const {672double arc_tol = 0.;673if (jointype == JoinType::Round) {674int n = circularSegments > 2 ? circularSegments675: Quality::GetCircularSegments(delta);676// This calculates tolerance as a function of circular segments and delta677// (radius) in order to get back the same number of segments in Clipper2:678// steps_per_360 = PI / acos(1 - arc_tol / abs_delta)679const double abs_delta = std::fabs(delta);680arc_tol = (std::cos(Clipper2Lib::PI / n) - 1) * -abs_delta;681}682auto ps =683C2::InflatePaths(GetPaths()->paths_, delta, jt(jointype),684C2::EndType::Polygon, miter_limit, precision_, arc_tol);685return CrossSection(shared_paths(ps));686}687688/**689* Compute the convex hull enveloping a set of cross-sections.690*691* @param crossSections A vector of cross-sections over which to compute a692* convex hull.693*/694CrossSection CrossSection::Hull(695const std::vector<CrossSection>& crossSections) {696int n = 0;697for (auto cs : crossSections) n += cs.NumVert();698SimplePolygon pts;699pts.reserve(n);700for (auto cs : crossSections) {701auto paths = cs.GetPaths()->paths_;702for (auto path : paths) {703for (auto p : path) {704pts.push_back(v2_of_pd(p));705}706}707}708return CrossSection(shared_paths(C2::PathsD{HullImpl(pts)}));709}710711/**712* Compute the convex hull of this cross-section.713*/714CrossSection CrossSection::Hull() const {715return Hull(std::vector<CrossSection>{*this});716}717718/**719* Compute the convex hull of a set of points. If the given points are fewer720* than 3, an empty CrossSection will be returned.721*722* @param pts A vector of 2-dimensional points over which to compute a convex723* hull.724*/725CrossSection CrossSection::Hull(SimplePolygon pts) {726return CrossSection(shared_paths(C2::PathsD{HullImpl(pts)}));727}728729/**730* Compute the convex hull of a set of points/polygons. If the given points are731* fewer than 3, an empty CrossSection will be returned.732*733* @param polys A vector of vectors of 2-dimensional points over which to734* compute a convex hull.735*/736CrossSection CrossSection::Hull(const Polygons polys) {737SimplePolygon pts;738for (auto poly : polys) {739for (auto p : poly) {740pts.push_back(p);741}742}743return Hull(pts);744}745746/**747* Return the total area covered by complex polygons making up the748* CrossSection.749*/750double CrossSection::Area() const { return C2::Area(GetPaths()->paths_); }751752/**753* Return the number of vertices in the CrossSection.754*/755size_t CrossSection::NumVert() const {756size_t n = 0;757auto paths = GetPaths()->paths_;758for (auto p : paths) {759n += p.size();760}761return n;762}763764/**765* Return the number of contours (both outer and inner paths) in the766* CrossSection.767*/768size_t CrossSection::NumContour() const { return GetPaths()->paths_.size(); }769770/**771* Does the CrossSection contain any contours?772*/773bool CrossSection::IsEmpty() const { return GetPaths()->paths_.empty(); }774775/**776* Returns the axis-aligned bounding rectangle of all the CrossSections'777* vertices.778*/779Rect CrossSection::Bounds() const {780auto r = C2::GetBounds(GetPaths()->paths_);781return Rect({r.left, r.bottom}, {r.right, r.top});782}783784/**785* Return the contours of this CrossSection as a Polygons.786*/787Polygons CrossSection::ToPolygons() const {788auto polys = Polygons();789auto paths = GetPaths()->paths_;790polys.reserve(paths.size());791for (auto p : paths) {792auto sp = SimplePolygon();793sp.reserve(p.size());794for (auto v : p) {795sp.push_back({v.x, v.y});796}797polys.push_back(sp);798}799return polys;800}801} // namespace manifold802803804