From a25fadaf73a0f1b1639f00007945d0956f7749c5 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 11 Apr 2024 15:36:07 +0200 Subject: [PATCH] Code cleaning CURA-9830 --- include/geometry/closed_polyline.h | 12 +- include/geometry/mixed_lines_set.h | 53 +- include/geometry/points_set.h | 8 +- include/geometry/polyline.h | 12 +- include/geometry/shape.h | 10 +- include/geometry/single_shape.h | 1 - src/FffGcodeWriter.cpp | 2 +- src/SkirtBrim.cpp | 2 +- src/geometry/closed_polyline.cpp | 12 + src/geometry/mixed_lines_set.cpp | 21 +- src/geometry/parts_view.cpp | 1 + src/geometry/points_set.cpp | 1 - src/geometry/polygon.cpp | 23 +- src/geometry/shape.cpp | 69 +- src/geometry/single_shape.cpp | 2 + src/multiVolumes.cpp | 1 + src/utils/Simplify.cpp | 2 +- src/utils/mixed_polyline_stitcher.cpp | 1 + src/utils/polygon.cpp | 1727 ------------------------- tests/InfillTest.cpp | 2 +- tests/LayerPlanTest.cpp | 60 +- tests/utils/AABBTest.cpp | 9 +- 22 files changed, 140 insertions(+), 1891 deletions(-) delete mode 100644 src/utils/polygon.cpp diff --git a/include/geometry/closed_polyline.h b/include/geometry/closed_polyline.h index 10db68cab3..f05f353ee2 100644 --- a/include/geometry/closed_polyline.h +++ b/include/geometry/closed_polyline.h @@ -50,17 +50,7 @@ class ClosedPolyline : public Polyline return ! explicitely_closed_; } - virtual size_t segmentsCount() const override - { - if (explicitely_closed_) - { - return size() >= 3 ? size() - 1 : 0; - } - else - { - return size() >= 2 ? size() : 0; - } - } + virtual size_t segmentsCount() const override; ClosedPolyline& operator=(const ClosedPolyline& other) { diff --git a/include/geometry/mixed_lines_set.h b/include/geometry/mixed_lines_set.h index a6d17bafb9..d67144bd69 100644 --- a/include/geometry/mixed_lines_set.h +++ b/include/geometry/mixed_lines_set.h @@ -19,37 +19,70 @@ class Shape; template class LinesSet; +using PolylinePtr = std::shared_ptr; +using OpenPolylinePtr = std::shared_ptr; /*! - * \brief Convenience definition for a container that can hold either open or closed polylines. + * \brief Convenience definition for a container that can hold any type of polyline. */ -class MixedLinesSet : public std::vector> +class MixedLinesSet : public std::vector { public: + /*! + * \brief Computes the offset of all the polylines contained in the set. The polylines may + * be of different types, and polylines are polygons are treated differently. + * \param distance The distance to increase the polylines from, or decrase if negative + * \param join_type The type of tip joint to be used (for open polylines only) + * \return A shape containing the offsetted polylines. This may contain many unjoined polygons, + * but no overlapping ones. + */ Shape offset(coord_t distance, ClipperLib::JoinType join_type = ClipperLib::jtMiter, double miter_limit = 1.2) const; + /*! @brief Adds a copy of the given polyline to the set + @note As we have to copy the whole points data, this is not very efficient */ void push_back(const OpenPolyline& line); + /*! @brief Adds a copy of the given polyline to the set + @note As we have to copy the whole points data, this is not very efficient */ + void push_back(const Polygon& line); + + /*! @brief Adds a copy of the given polyline to the set + * @note As we can move the points data, this is much more efficient than the above methods */ void push_back(OpenPolyline&& line); + /*! @brief Adds a copy of the given polyline to the set + * @note As we can move the points data, this is much more efficient than the above methods */ void push_back(ClosedPolyline&& line); - void push_back(const Polygon& line); + /*! @brief Adds the given shared pointer to the set. The pointer reference count will be incremeted but no data is actually copied. + * @note The is even more efficient than the above methods because it only involves copying a pointer */ + void push_back(const OpenPolylinePtr& line); - void push_back(const std::shared_ptr& line); + /*! @brief Adds the given shared pointer to the set. The pointer reference count will be incremeted but no data is actually copied. + * @note The is even more efficient than the above methods because it only involves copying a pointer */ + void push_back(const PolylinePtr& line); - void push_back(const std::shared_ptr& line); + /*! @brief Adds a copy of all the polygons contained in the shape + @note As we have to copy the whole points data, this is really not efficient */ + void push_back(const Shape& shape); - void push_back(LinesSet&& lines_set); + /*! @brief Adds a copy of all the polygons contained in the set + @note As we have to copy the whole points data, this is really not efficient */ + void push_back(const LinesSet& lines_set); + /*! @brief Adds a copy of all the polylines contained in the set + @note As we have to copy the whole points data, this is really not efficient */ void push_back(const LinesSet& lines_set); - void push_back(LinesSet&& lines_set); - - void push_back(const LinesSet& lines_set); + /*! @brief Adds a copy of all the polylines contained in the set + * @note As we can move the points data, this is much more efficient than the above methods */ + void push_back(LinesSet&& lines_set); - void push_back(const Shape& shape); + /*! @brief Adds a copy of all the polylines contained in the set + * @note As we can move the points data, this is much more efficient than the above methods */ + void push_back(LinesSet&& lines_set); + /*! \brief Computes the total lenght of all the polylines in the set */ coord_t length() const; }; diff --git a/include/geometry/points_set.h b/include/geometry/points_set.h index c0772d6252..3eb151fd59 100644 --- a/include/geometry/points_set.h +++ b/include/geometry/points_set.h @@ -22,7 +22,7 @@ const static int clipper_init = (0); class PointsSet { private: - std::vector points_; + ClipperLib::Path points_; public: PointsSet() = default; @@ -37,17 +37,17 @@ class PointsSet PointsSet(ClipperLib::Path&& points); - const std::vector& getPoints() const + const ClipperLib::Path& getPoints() const { return points_; } - std::vector& getPoints() + ClipperLib::Path& getPoints() { return points_; } - void setPoints(std::vector&& points) + void setPoints(ClipperLib::Path&& points) { points_ = points; } diff --git a/include/geometry/polyline.h b/include/geometry/polyline.h index 6473811083..a51b13240b 100644 --- a/include/geometry/polyline.h +++ b/include/geometry/polyline.h @@ -47,7 +47,7 @@ class Polyline : public PointsSet { } - Polyline(const std::vector& points) + Polyline(const ClipperLib::Path& points) : PointsSet(points) { } @@ -59,8 +59,18 @@ class Polyline : public PointsSet virtual ~Polyline() = default; + /*! + * \brief Indicates whether this polyline has a virtual closing segment between the last point + * in the set and the first one + * \return True if a segment between the last and first point should be considered + */ virtual bool addClosingSegment() const = 0; + /*! + * \brief Gets the total number of "full" segments in the polyline. Calling this is also safe if + * there are not enough points to make a valid polyline, so it can also be a good + * indicator of a "valid" polyline. + */ virtual size_t segmentsCount() const = 0; Polyline& operator=(const Polyline& other) diff --git a/include/geometry/shape.h b/include/geometry/shape.h index ee5b168a6d..16bfb8d59b 100644 --- a/include/geometry/shape.h +++ b/include/geometry/shape.h @@ -5,7 +5,6 @@ #define GEOMETRY_SHAPE_H #include "geometry/lines_set.h" -#include "geometry/polygon.h" #include "settings/types/Angle.h" namespace cura @@ -15,6 +14,8 @@ class Polygon; class Ratio; class SingleShape; class PartsView; +class PointMatrix; +class Point3Matrix; class Shape : public LinesSet { @@ -29,10 +30,7 @@ class Shape : public LinesSet Shape(Shape&& other) = default; - Shape(const std::initializer_list& initializer) - : LinesSet(initializer) - { - } + Shape(const std::initializer_list& initializer); explicit Shape(ClipperLib::Paths&& paths, bool explicitely_closed = clipper_explicitely_closed_); @@ -79,9 +77,9 @@ class Shape : public LinesSet * \param restitch Whether to stitch the resulting segments into longer polylines, or leave every segment as a single segment * \param max_stitch_distance The maximum distance for two polylines to be stitched together with a segment * \return The resulting polylines limited to the area of this Polygons object + * \todo This should technically return a MixedLinesSet, because it can definitely contain open and closed polylines, but that is a heavy change */ template -#warning Technically this should return a MixedLinesSet LinesSet intersection(const LinesSet& polylines, bool restitch = true, const coord_t max_stitch_distance = 10_mu) const; /*! diff --git a/include/geometry/single_shape.h b/include/geometry/single_shape.h index d1eb0895d6..472302d01f 100644 --- a/include/geometry/single_shape.h +++ b/include/geometry/single_shape.h @@ -5,7 +5,6 @@ #define GEOMETRY_SINGLE_SHAPE_H #include "geometry/shape.h" -#include "point2ll.h" namespace cura { diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 45c19d6533..74c6a90ed9 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1393,7 +1393,7 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan push_lines(offset.open_polylines); */ - for (const std::shared_ptr& line : offset) + for (const PolylinePtr& line : offset) { if (line->segmentsCount() > 0) { diff --git a/src/SkirtBrim.cpp b/src/SkirtBrim.cpp index cee9bc4caf..cc1177b819 100644 --- a/src/SkirtBrim.cpp +++ b/src/SkirtBrim.cpp @@ -261,7 +261,7 @@ coord_t SkirtBrim::generateOffset(const Offset& offset, Shape& covered_area, std std::remove_if( result.begin(), result.end(), - [](const std::shared_ptr& line) + [](const PolylinePtr& line) { if (const std::shared_ptr open_line = dynamic_pointer_cast(line)) { diff --git a/src/geometry/closed_polyline.cpp b/src/geometry/closed_polyline.cpp index cdd3689551..f08cf9624e 100644 --- a/src/geometry/closed_polyline.cpp +++ b/src/geometry/closed_polyline.cpp @@ -8,6 +8,18 @@ namespace cura { +size_t ClosedPolyline::segmentsCount() const +{ + if (explicitely_closed_) + { + return size() >= 3 ? size() - 1 : 0; + } + else + { + return size() >= 2 ? size() : 0; + } +} + bool ClosedPolyline::inside(const Point2LL& p, bool border_result) const { int res = ClipperLib::PointInPolygon(p, getPoints()); diff --git a/src/geometry/mixed_lines_set.cpp b/src/geometry/mixed_lines_set.cpp index 2ad346e598..651c7bc927 100644 --- a/src/geometry/mixed_lines_set.cpp +++ b/src/geometry/mixed_lines_set.cpp @@ -6,6 +6,7 @@ #include #include "geometry/open_polyline.h" +#include "geometry/polygon.h" #include "geometry/shape.h" @@ -19,7 +20,7 @@ Shape MixedLinesSet::offset(coord_t distance, ClipperLib::JoinType join_type, do // Return a shape that contains only actual polygons Shape result; - for (const std::shared_ptr& line : (*this)) + for (const PolylinePtr& line : (*this)) { if (const std::shared_ptr polygon = dynamic_pointer_cast(line)) { @@ -34,7 +35,7 @@ Shape MixedLinesSet::offset(coord_t distance, ClipperLib::JoinType join_type, do Shape polygons; ClipperLib::ClipperOffset clipper(miter_limit, 10.0); - for (const std::shared_ptr& line : (*this)) + for (const PolylinePtr& line : (*this)) { if (const std::shared_ptr polygon = dynamic_pointer_cast(line)) { @@ -82,32 +83,32 @@ Shape MixedLinesSet::offset(coord_t distance, ClipperLib::JoinType join_type, do void MixedLinesSet::push_back(const OpenPolyline& line) { - std::vector>::push_back(std::make_shared(line)); + std::vector::push_back(std::make_shared(line)); } void MixedLinesSet::push_back(OpenPolyline&& line) { - std::vector>::push_back(std::make_shared(std::move(line))); + std::vector::push_back(std::make_shared(std::move(line))); } void MixedLinesSet::push_back(ClosedPolyline&& line) { - std::vector>::push_back(std::make_shared(std::move(line))); + std::vector::push_back(std::make_shared(std::move(line))); } void MixedLinesSet::push_back(const Polygon& line) { - std::vector>::push_back(std::make_shared(std::move(line))); + std::vector::push_back(std::make_shared(std::move(line))); } void MixedLinesSet::push_back(const std::shared_ptr& line) { - std::vector>::push_back(line); + std::vector::push_back(line); } -void MixedLinesSet::push_back(const std::shared_ptr& line) +void MixedLinesSet::push_back(const PolylinePtr& line) { - std::vector>::push_back(line); + std::vector::push_back(line); } void MixedLinesSet::push_back(LinesSet&& lines_set) @@ -157,7 +158,7 @@ coord_t MixedLinesSet::length() const begin(), end(), coord_t(0), - [](coord_t value, const std::shared_ptr& line) + [](coord_t value, const PolylinePtr& line) { return value + line->length(); }); diff --git a/src/geometry/parts_view.cpp b/src/geometry/parts_view.cpp index 0422cd0d92..7d84d2d757 100644 --- a/src/geometry/parts_view.cpp +++ b/src/geometry/parts_view.cpp @@ -3,6 +3,7 @@ #include "geometry/parts_view.h" +#include "geometry/polygon.h" #include "geometry/single_shape.h" namespace cura diff --git a/src/geometry/points_set.cpp b/src/geometry/points_set.cpp index 4712b97f52..29275d2117 100644 --- a/src/geometry/points_set.cpp +++ b/src/geometry/points_set.cpp @@ -3,7 +3,6 @@ #include "geometry/points_set.h" -#include "geometry/point2ll.h" #include "geometry/point3_matrix.h" #include "geometry/point_matrix.h" diff --git a/src/geometry/polygon.cpp b/src/geometry/polygon.cpp index 82f796dc4b..928684fdb6 100644 --- a/src/geometry/polygon.cpp +++ b/src/geometry/polygon.cpp @@ -3,30 +3,11 @@ #include "geometry/polygon.h" -// #include - -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -#include "utils/ListPolyIt.h" -#include "utils/linearAlg2D.h" - -// #include "utils/PolylineStitcher.h" #include "geometry/point3_matrix.h" #include "geometry/point_matrix.h" #include "geometry/shape.h" +#include "utils/ListPolyIt.h" +#include "utils/linearAlg2D.h" namespace cura { diff --git a/src/geometry/shape.cpp b/src/geometry/shape.cpp index 10ef1e2663..e0d893eef6 100644 --- a/src/geometry/shape.cpp +++ b/src/geometry/shape.cpp @@ -4,24 +4,15 @@ #include "geometry/shape.h" #include +#include #include -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include -#include -#include "geometry/closed_polyline.h" #include "geometry/mixed_lines_set.h" #include "geometry/parts_view.h" +#include "geometry/polygon.h" #include "geometry/single_shape.h" #include "settings/types/Ratio.h" #include "utils/OpenPolylineStitcher.h" @@ -35,6 +26,11 @@ Shape::Shape(ClipperLib::Paths&& paths, bool explicitely_closed) emplace_back(std::move(paths), explicitely_closed); } +Shape::Shape(const std::initializer_list& initializer) + : LinesSet(initializer) +{ +} + void Shape::emplace_back(ClipperLib::Paths&& paths, bool explicitely_closed) { reserve(size() + paths.size()); @@ -546,58 +542,7 @@ Shape Shape::toPolygons(ClipperLib::PolyTree& poly_tree) ClipperLib::PolyTreeToPaths(poly_tree, ret); return Shape(std::move(ret)); } -/* -[[maybe_unused]] Shape Shape::fromWkt(const std::string& wkt) -{ - typedef boost::geometry::model::d2::point_xy point_type; - typedef boost::geometry::model::polygon polygon_type; - - polygon_type poly; - boost::geometry::read_wkt(wkt, poly); - - Shape ret; - - Polygon outer; - for (const auto& point : poly.outer()) - { - outer.push_back(Point2LL(point.x(), point.y())); - } - ret.push_back(outer); - - for (const auto& hole : poly.inners()) - { - Polygon inner; - for (const auto& point : hole) - { - inner.push_back(Point2LL(point.x(), point.y())); - } - ret.push_back(inner); - } - - return ret; -} -[[maybe_unused]] void Shape::writeWkt(std::ostream& stream) const -{ - stream << "POLYGON ("; - const auto paths_str = (*this) - | ranges::views::transform( - [](const auto& path) - { - const auto line_string = ranges::views::concat(path, path | ranges::views::take(1)) - | ranges::views::transform( - [](const auto& point) - { - return fmt::format("{} {}", point.X, point.Y); - }) - | ranges::views::join(ranges::views::c_str(", ")) | ranges::to(); - return "(" + line_string + ")"; - }) - | ranges::views::join(ranges::views::c_str(", ")) | ranges::to(); - stream << paths_str; - stream << ")"; -} -*/ Shape Shape::smooth_outward(const AngleDegrees max_angle, int shortcut_length) const { Shape ret; diff --git a/src/geometry/single_shape.cpp b/src/geometry/single_shape.cpp index 09f28285dd..17205811e1 100644 --- a/src/geometry/single_shape.cpp +++ b/src/geometry/single_shape.cpp @@ -3,6 +3,8 @@ #include "geometry/single_shape.h" +#include "geometry/polygon.h" + namespace cura { diff --git a/src/multiVolumes.cpp b/src/multiVolumes.cpp index e89e2a3d19..6c607a55c9 100644 --- a/src/multiVolumes.cpp +++ b/src/multiVolumes.cpp @@ -7,6 +7,7 @@ #include "Application.h" #include "Slice.h" +#include "geometry/polygon.h" #include "settings/EnumSettings.h" #include "settings/types/LayerIndex.h" #include "slicer.h" diff --git a/src/utils/Simplify.cpp b/src/utils/Simplify.cpp index c215464cd2..7ec3e3e17d 100644 --- a/src/utils/Simplify.cpp +++ b/src/utils/Simplify.cpp @@ -63,7 +63,7 @@ LinesSet Simplify::polyline(const LinesSet& polylines) const MixedLinesSet Simplify::polyline(const MixedLinesSet& polylines) const { MixedLinesSet result; - for (const std::shared_ptr& polyline_ptr : polylines) + for (const PolylinePtr& polyline_ptr : polylines) { if (std::shared_ptr open_polyline = std::dynamic_pointer_cast(polyline_ptr)) { diff --git a/src/utils/mixed_polyline_stitcher.cpp b/src/utils/mixed_polyline_stitcher.cpp index 8095231d73..8792036108 100644 --- a/src/utils/mixed_polyline_stitcher.cpp +++ b/src/utils/mixed_polyline_stitcher.cpp @@ -4,6 +4,7 @@ #include "utils/mixed_polyline_stitcher.h" #include "geometry/mixed_lines_set.h" +#include "geometry/polygon.h" #include "geometry/shape.h" diff --git a/src/utils/polygon.cpp b/src/utils/polygon.cpp deleted file mode 100644 index 99cb2be1b9..0000000000 --- a/src/utils/polygon.cpp +++ /dev/null @@ -1,1727 +0,0 @@ -// Copyright (c) 2024 UltiMaker -// CuraEngine is released under the terms of the AGPLv3 or higher. - -#include "utils/polygon.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "utils/ListPolyIt.h" -#include "utils/PolylineStitcher.h" -#include "utils/linearAlg2D.h" // pointLiesOnTheRightOfLine - -namespace cura -{ - -size_t ConstPolygonRef::size() const -{ - return path->size(); -} - -bool ConstPolygonRef::empty() const -{ - return path->empty(); -} - -bool ConstPolygonRef::shorterThan(const coord_t check_length) const -{ - return cura::shorterThan(*this, check_length); -} - -bool ConstPolygonRef::_inside(Point2LL p, bool border_result) const -{ - const ConstPolygonRef thiss = *this; - if (size() < 1) - { - return false; - } - - int crossings = 0; - Point2LL p0 = back(); - for (unsigned int n = 0; n < size(); n++) - { - Point2LL p1 = thiss[n]; - // no tests unless the segment p0-p1 is at least partly at, or to right of, p.X - short comp = LinearAlg2D::pointLiesOnTheRightOfLine(p, p0, p1); - if (comp == 1) - { - crossings++; - } - else if (comp == 0) - { - return border_result; - } - p0 = p1; - } - return (crossings % 2) == 1; -} - - -Polygons ConstPolygonRef::intersection(const ConstPolygonRef& other) const -{ - Polygons ret; - ClipperLib::Clipper clipper(clipper_init); - clipper.AddPath(*path, ClipperLib::ptSubject, true); - clipper.AddPath(*other.path, ClipperLib::ptClip, true); - clipper.Execute(ClipperLib::ctIntersection, ret.paths); - return ret; -} - -bool Polygons::empty() const -{ - return paths.empty(); -} - -Polygons Polygons::approxConvexHull(int extra_outset) -{ - constexpr int overshoot = MM2INT(100); // 10cm (hard-coded value). - - Polygons convex_hull; - // Perform the offset for each polygon one at a time. - // This is necessary because the polygons may overlap, in which case the offset could end up in an infinite loop. - // See http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/_Body.htm - for (const ClipperLib::Path& path : paths) - { - Polygons offset_result; - ClipperLib::ClipperOffset offsetter(1.2, 10.0); - offsetter.AddPath(path, ClipperLib::jtRound, ClipperLib::etClosedPolygon); - offsetter.Execute(offset_result.paths, overshoot); - convex_hull.add(offset_result); - } - return convex_hull.unionPolygons().offset(-overshoot + extra_outset, ClipperLib::jtRound); -} - -void Polygons::makeConvex() -{ - // early out if there is nothing to do - if (empty()) - { - return; - } - - // Andrew’s Monotone Chain Convex Hull Algorithm - std::vector points; - - for (const auto& poly : this->paths) - { - points.insert(points.end(), poly.begin(), poly.end()); - } - - ClipperLib::Path convexified; - auto make_sorted_poly_convex = [&convexified](std::vector& poly) - { - convexified.push_back(poly[0]); - - for (const auto window : poly | ranges::views::sliding(2)) - { - const Point2LL& current = window[0]; - const Point2LL& after = window[1]; - - if (LinearAlg2D::pointIsLeftOfLine(current, convexified.back(), after) < 0) - { - // Track backwards to make sure we haven't been in a concave pocket for multiple vertices already. - while (convexified.size() >= 2 - && (LinearAlg2D::pointIsLeftOfLine(convexified.back(), convexified[convexified.size() - 2], current) >= 0 - || LinearAlg2D::pointIsLeftOfLine(convexified.back(), convexified[convexified.size() - 2], convexified.front()) > 0)) - { - convexified.pop_back(); - } - convexified.push_back(current); - } - } - }; - - std::sort( - points.begin(), - points.end(), - [](Point2LL a, Point2LL b) - { - return a.X == b.X ? a.Y < b.Y : a.X < b.X; - }); - make_sorted_poly_convex(points); - std::reverse(points.begin(), points.end()); - make_sorted_poly_convex(points); - - this->paths = { convexified }; -} - -size_t Polygons::pointCount() const -{ - size_t count = 0; - for (const ClipperLib::Path& path : paths) - { - count += path.size(); - } - return count; -} - -bool Polygons::inside(Point2LL p, bool border_result) const -{ - int poly_count_inside = 0; - for (const ClipperLib::Path& poly : *this) - { - const int is_inside_this_poly = ClipperLib::PointInPolygon(p, poly); - if (is_inside_this_poly == -1) - { - return border_result; - } - poly_count_inside += is_inside_this_poly; - } - return (poly_count_inside % 2) == 1; -} - -bool PolygonsPart::inside(Point2LL p, bool border_result) const -{ - if (size() < 1) - { - return false; - } - if (! (*this)[0].inside(p, border_result)) - { - return false; - } - for (unsigned int n = 1; n < paths.size(); n++) - { - if ((*this)[n].inside(p, border_result)) - { - return false; - } - } - return true; -} - -bool Polygons::insideOld(Point2LL p, bool border_result) const -{ - const Polygons& thiss = *this; - if (size() < 1) - { - return false; - } - - int crossings = 0; - for (const ClipperLib::Path& poly : thiss) - { - Point2LL p0 = poly.back(); - for (const Point2LL& p1 : poly) - { - short comp = LinearAlg2D::pointLiesOnTheRightOfLine(p, p0, p1); - if (comp == 1) - { - crossings++; - } - else if (comp == 0) - { - return border_result; - } - p0 = p1; - } - } - return (crossings % 2) == 1; -} - -size_t Polygons::findInside(Point2LL p, bool border_result) -{ - Polygons& thiss = *this; - if (size() < 1) - { - return false; - } - - // NOTE: Keep these vectors fixed-size, they replace an (non-standard, sized at runtime) arrays. - std::vector min_x(size(), std::numeric_limits::max()); - std::vector crossings(size()); - - for (size_t poly_idx = 0; poly_idx < size(); poly_idx++) - { - PolygonRef poly = thiss[poly_idx]; - Point2LL p0 = poly.back(); - for (Point2LL& p1 : poly) - { - short comp = LinearAlg2D::pointLiesOnTheRightOfLine(p, p0, p1); - if (comp == 1) - { - crossings[poly_idx]++; - int64_t x; - if (p1.Y == p0.Y) - { - x = p0.X; - } - else - { - x = p0.X + (p1.X - p0.X) * (p.Y - p0.Y) / (p1.Y - p0.Y); - } - if (x < min_x[poly_idx]) - { - min_x[poly_idx] = x; - } - } - else if (border_result && comp == 0) - { - return poly_idx; - } - p0 = p1; - } - } - - int64_t min_x_uneven = std::numeric_limits::max(); - size_t ret = NO_INDEX; - size_t n_unevens = 0; - for (size_t array_idx = 0; array_idx < size(); array_idx++) - { - if (crossings[array_idx] % 2 == 1) - { - n_unevens++; - if (min_x[array_idx] < min_x_uneven) - { - min_x_uneven = min_x[array_idx]; - ret = array_idx; - } - } - } - if (n_unevens % 2 == 0) - { - ret = NO_INDEX; - } - return ret; -} - -Polygons Polygons::intersectionPolyLines(const Polygons& polylines, bool restitch, const coord_t max_stitch_distance) const -{ - Polygons split_polylines = polylines.splitPolylinesIntoSegments(); - - ClipperLib::PolyTree result; - ClipperLib::Clipper clipper(clipper_init); - clipper.AddPaths(split_polylines.paths, ClipperLib::ptSubject, false); - clipper.AddPaths(paths, ClipperLib::ptClip, true); - clipper.Execute(ClipperLib::ctIntersection, result); - Polygons ret; - ClipperLib::OpenPathsFromPolyTree(result, ret.paths); - - if (restitch) - { - Polygons result_lines, result_polygons; - const coord_t snap_distance = 10_mu; - PolylineStitcher::stitch(ret, result_lines, result_polygons, max_stitch_distance, snap_distance); - ret = result_lines; - // if polylines got stitched into polygons, split them back up into a polyline again, because the result only admits polylines - for (PolygonRef poly : result_polygons) - { - if (poly.empty()) - continue; - if (poly.size() > 2) - { - poly.emplace_back(poly[0]); - } - ret.add(poly); - } - } - - return ret; -} - -void Polygons::toPolylines() -{ - for (PolygonRef poly : *this) - { - if (poly.empty()) - continue; - poly.emplace_back(poly.front()); - } -} - -void Polygons::splitPolylinesIntoSegments(Polygons& result) const -{ - for (ConstPolygonRef poly : *this) - { - poly.splitPolylineIntoSegments(result); - } -} -Polygons Polygons::splitPolylinesIntoSegments() const -{ - Polygons ret; - splitPolylinesIntoSegments(ret); - return ret; -} - -void Polygons::splitPolygonsIntoSegments(Polygons& result) const -{ - for (ConstPolygonRef poly : *this) - { - poly.splitPolygonIntoSegments(result); - } -} -Polygons Polygons::splitPolygonsIntoSegments() const -{ - Polygons ret; - splitPolygonsIntoSegments(ret); - return ret; -} - -coord_t Polygons::polyLineLength() const -{ - coord_t length = 0; - for (ConstPolygonRef poly : *this) - { - length += poly.polylineLength(); - } - return length; -} - -Polygons Polygons::offset(coord_t distance, ClipperLib::JoinType join_type, double miter_limit) const -{ - if (distance == 0) - { - return *this; - } - Polygons ret; - ClipperLib::ClipperOffset clipper(miter_limit, 10.0); - clipper.AddPaths(unionPolygons().paths, join_type, ClipperLib::etClosedPolygon); - clipper.MiterLimit = miter_limit; - clipper.Execute(ret.paths, distance); - return ret; -} - -Polygons Polygons::offset(const std::vector& offset_dists) const -{ - // we need as many offset-dists as points - assert(this->pointCount() == offset_dists.size()); - - Polygons ret; - int i = 0; - for (auto& poly_line : this->paths - | ranges::views::filter( - [](const auto& path) - { - return ! path.empty(); - })) - { - std::vector ret_poly_line; - - auto prev_p = poly_line.back(); - auto prev_dist = offset_dists[i + poly_line.size() - 1]; - - for (const auto& p : poly_line) - { - auto offset_dist = offset_dists[i]; - - auto vec_dir = prev_p - p; - - constexpr coord_t min_vec_len = 10; - if (vSize2(vec_dir) > min_vec_len * min_vec_len) - { - auto offset_p1 = turn90CCW(normal(vec_dir, prev_dist)); - auto offset_p2 = turn90CCW(normal(vec_dir, offset_dist)); - - ret_poly_line.emplace_back(prev_p + offset_p1); - ret_poly_line.emplace_back(p + offset_p2); - } - - prev_p = p; - prev_dist = offset_dist; - i++; - } - - ret.add(ret_poly_line); - } - - ClipperLib::SimplifyPolygons(ret.paths, ClipperLib::PolyFillType::pftPositive); - - return ret; -} - -Polygons ConstPolygonRef::offset(int distance, ClipperLib::JoinType join_type, double miter_limit) const -{ - if (distance == 0) - { - Polygons ret; - ret.add(*this); - return ret; - } - Polygons ret; - ClipperLib::ClipperOffset clipper(miter_limit, 10.0); - clipper.AddPath(*path, join_type, ClipperLib::etClosedPolygon); - clipper.MiterLimit = miter_limit; - clipper.Execute(ret.paths, distance); - return ret; -} - -void PolygonRef::removeColinearEdges(const AngleRadians max_deviation_angle) -{ - // TODO: Can be made more efficient (for example, use pointer-types for process-/skip-indices, so we can swap them without copy). - - size_t num_removed_in_iteration = 0; - do - { - num_removed_in_iteration = 0; - - std::vector process_indices(path->size(), true); - - bool go = true; - while (go) - { - go = false; - - const auto& rpath = *path; - const size_t pathlen = rpath.size(); - if (pathlen <= 3) - { - return; - } - - std::vector skip_indices(path->size(), false); - - ClipperLib::Path new_path; - for (size_t point_idx = 0; point_idx < pathlen; ++point_idx) - { - // Don't iterate directly over process-indices, but do it this way, because there are points _in_ process-indices that should nonetheless be skipped: - if (! process_indices[point_idx]) - { - new_path.push_back(rpath[point_idx]); - continue; - } - - // Should skip the last point for this iteration if the old first was removed (which can be seen from the fact that the new first was skipped): - if (point_idx == (pathlen - 1) && skip_indices[0]) - { - skip_indices[new_path.size()] = true; - go = true; - new_path.push_back(rpath[point_idx]); - break; - } - - const Point2LL& prev = rpath[(point_idx - 1 + pathlen) % pathlen]; - const Point2LL& pt = rpath[point_idx]; - const Point2LL& next = rpath[(point_idx + 1) % pathlen]; - - double angle = LinearAlg2D::getAngleLeft(prev, pt, next); // [0 : 2 * pi] - if (angle >= std::numbers::pi) - { - angle -= std::numbers::pi; - } // map [pi : 2 * pi] to [0 : pi] - - // Check if the angle is within limits for the point to 'make sense', given the maximum deviation. - // If the angle indicates near-parallel segments ignore the point 'pt' - if (angle > max_deviation_angle && angle < std::numbers::pi - max_deviation_angle) - { - new_path.push_back(pt); - } - else if (point_idx != (pathlen - 1)) - { - // Skip the next point, since the current one was removed: - skip_indices[new_path.size()] = true; - go = true; - new_path.push_back(next); - ++point_idx; - } - } - *path = new_path; - num_removed_in_iteration += pathlen - path->size(); - - process_indices.clear(); - process_indices.insert(process_indices.end(), skip_indices.begin(), skip_indices.end()); - } - } while (num_removed_in_iteration > 0); -} - -void PolygonRef::applyMatrix(const PointMatrix& matrix) -{ - for (unsigned int path_idx = 0; path_idx < path->size(); path_idx++) - { - (*path)[path_idx] = matrix.apply((*path)[path_idx]); - } -} -void PolygonRef::applyMatrix(const Point3Matrix& matrix) -{ - for (unsigned int path_idx = 0; path_idx < path->size(); path_idx++) - { - (*path)[path_idx] = matrix.apply((*path)[path_idx]); - } -} - -Polygons Polygons::getOutsidePolygons() const -{ - Polygons ret; - ClipperLib::Clipper clipper(clipper_init); - ClipperLib::PolyTree poly_tree; - constexpr bool paths_are_closed_polys = true; - clipper.AddPaths(paths, ClipperLib::ptSubject, paths_are_closed_polys); - clipper.Execute(ClipperLib::ctUnion, poly_tree); - - for (int outer_poly_idx = 0; outer_poly_idx < poly_tree.ChildCount(); outer_poly_idx++) - { - ClipperLib::PolyNode* child = poly_tree.Childs[outer_poly_idx]; - ret.emplace_back(child->Contour); - } - return ret; -} - -void Polygons::removeEmptyHoles_processPolyTreeNode(const ClipperLib::PolyNode& node, const bool remove_holes, Polygons& ret) const -{ - for (int outer_poly_idx = 0; outer_poly_idx < node.ChildCount(); outer_poly_idx++) - { - ClipperLib::PolyNode* child = node.Childs[outer_poly_idx]; - if (remove_holes) - { - ret.emplace_back(child->Contour); - } - for (int hole_node_idx = 0; hole_node_idx < child->ChildCount(); hole_node_idx++) - { - ClipperLib::PolyNode& hole_node = *child->Childs[hole_node_idx]; - if ((hole_node.ChildCount() > 0) == remove_holes) - { - ret.emplace_back(hole_node.Contour); - removeEmptyHoles_processPolyTreeNode(hole_node, remove_holes, ret); - } - } - } -} - -void Polygons::removeSmallAreas(const double min_area_size, const bool remove_holes) -{ - auto new_end = paths.end(); - if (remove_holes) - { - for (auto it = paths.begin(); it < new_end;) - { - // All polygons smaller than target are removed by replacing them with a polygon from the back of the vector - if (std::abs(INT2MM2(ClipperLib::Area(*it))) < min_area_size) - { - *it = std::move(*--new_end); - continue; - } - it++; // Skipped on removal such that the polygon just swaped in is checked next - } - } - else - { - // For each polygon, computes the signed area, move small outlines at the end of the vector and keep references on small holes - std::vector small_holes; - for (auto it = paths.begin(); it < new_end;) - { - double area = INT2MM2(ClipperLib::Area(*it)); - if (std::abs(area) < min_area_size) - { - if (area >= 0) - { - --new_end; - if (it < new_end) - { - std::swap(*new_end, *it); - continue; - } - else - { // Don't self-swap the last Path - break; - } - } - else - { - small_holes.push_back(*it); - } - } - it++; // Skipped on removal such that the polygon just swaped in is checked next - } - - // Removes small holes that have their first point inside one of the removed outlines - // Iterating in reverse ensures that unprocessed small holes won't be moved - const auto removed_outlines_start = new_end; - for (auto hole_it = small_holes.rbegin(); hole_it < small_holes.rend(); hole_it++) - { - for (auto outline_it = removed_outlines_start; outline_it < paths.end(); outline_it++) - { - if (PolygonRef(*outline_it).inside(*hole_it->begin())) - { - **hole_it = std::move(*--new_end); - break; - } - } - } - } - paths.resize(new_end - paths.begin()); -} - -void Polygons::removeSmallCircumference(const coord_t min_circumference_size, const bool remove_holes) -{ - removeSmallAreaCircumference(0.0, min_circumference_size, remove_holes); -} - -void Polygons::removeSmallAreaCircumference(const double min_area_size, const coord_t min_circumference_size, const bool remove_holes) -{ - Polygons new_polygon; - - bool outline_is_removed = false; - for (ConstPolygonRef poly : paths) - { - double area = poly.area(); - auto circumference = poly.polygonLength(); - bool is_outline = area >= 0; - - if (is_outline) - { - if (circumference >= min_circumference_size && std::abs(area) >= min_area_size) - { - new_polygon.add(poly); - outline_is_removed = false; - } - else - { - outline_is_removed = true; - } - } - else if (outline_is_removed) - { - // containing parent outline is removed; hole should be removed as well - } - else if (! remove_holes || (circumference >= min_circumference_size && std::abs(area) >= min_area_size)) - { - // keep hole-polygon if we do not remove holes, or if its - // circumference is bigger then the minimum circumference size - new_polygon.add(poly); - } - } - - *this = new_polygon; -} - -void Polygons::removeDegenerateVerts() -{ - _removeDegenerateVerts(false); -} - -void Polygons::removeDegenerateVertsPolyline() -{ - _removeDegenerateVerts(true); -} - -void Polygons::_removeDegenerateVerts(const bool for_polyline) -{ - Polygons& thiss = *this; - for (size_t poly_idx = 0; poly_idx < size(); poly_idx++) - { - PolygonRef poly = thiss[poly_idx]; - Polygon result; - - auto isDegenerate = [](const Point2LL& last, const Point2LL& now, const Point2LL& next) - { - Point2LL last_line = now - last; - Point2LL next_line = next - now; - return dot(last_line, next_line) == -1 * vSize(last_line) * vSize(next_line); - }; - - // With polylines, skip the first and last vertex. - const size_t start_vertex = for_polyline ? 1 : 0; - const size_t end_vertex = for_polyline ? poly.size() - 1 : poly.size(); - for (size_t i = 0; i < start_vertex; ++i) - { - result.add(poly[i]); // Add everything before the start vertex. - } - - bool isChanged = false; - for (size_t idx = start_vertex; idx < end_vertex; idx++) - { - const Point2LL& last = (result.size() == 0) ? poly.back() : result.back(); - if (idx + 1 >= poly.size() && result.size() == 0) - { - break; - } - const Point2LL& next = (idx + 1 >= poly.size()) ? result[0] : poly[idx + 1]; - if (isDegenerate(last, poly[idx], next)) - { // lines are in the opposite direction - // don't add vert to the result - isChanged = true; - while (result.size() > 1 && isDegenerate(result[result.size() - 2], result.back(), next)) - { - result.pop_back(); - } - } - else - { - result.add(poly[idx]); - } - } - - for (size_t i = end_vertex; i < poly.size(); ++i) - { - result.add(poly[i]); // Add everything after the end vertex. - } - - if (isChanged) - { - if (for_polyline || result.size() > 2) - { - *poly = *result; - } - else - { - thiss.remove(poly_idx); - poly_idx--; // effectively the next iteration has the same poly_idx (referring to a new poly which is not yet processed) - } - } - } -} - -Polygons Polygons::toPolygons(ClipperLib::PolyTree& poly_tree) -{ - Polygons ret; - ClipperLib::PolyTreeToPaths(poly_tree, ret.paths); - return ret; -} - -[[maybe_unused]] Polygons Polygons::fromWkt(const std::string& wkt) -{ - typedef boost::geometry::model::d2::point_xy point_type; - typedef boost::geometry::model::polygon polygon_type; - - polygon_type poly; - boost::geometry::read_wkt(wkt, poly); - - Polygons ret; - - Polygon outer; - for (const auto& point : poly.outer()) - { - outer.add(Point2LL(point.x(), point.y())); - } - ret.add(outer); - - for (const auto& hole : poly.inners()) - { - Polygon inner; - for (const auto& point : hole) - { - inner.add(Point2LL(point.x(), point.y())); - } - ret.add(inner); - } - - return ret; -} - -[[maybe_unused]] void Polygons::writeWkt(std::ostream& stream) const -{ - stream << "POLYGON ("; - const auto paths_str = paths - | ranges::views::transform( - [](const auto& path) - { - const auto line_string = ranges::views::concat(path, path | ranges::views::take(1)) - | ranges::views::transform( - [](const auto& point) - { - return fmt::format("{} {}", point.X, point.Y); - }) - | ranges::views::join(ranges::views::c_str(", ")) | ranges::to(); - return "(" + line_string + ")"; - }) - | ranges::views::join(ranges::views::c_str(", ")) | ranges::to(); - stream << paths_str; - stream << ")"; -} - -bool ConstPolygonRef::smooth_corner_complex(const Point2LL p1, ListPolyIt& p0_it, ListPolyIt& p2_it, const int64_t shortcut_length) -{ - // walk away from the corner until the shortcut > shortcut_length or it would smooth a piece inward - // - walk in both directions untill shortcut > shortcut_length - // - stop walking in one direction if it would otherwise cut off a corner in that direction - // - same in the other direction - // - stop if both are cut off - // walk by updating p0_it and p2_it - int64_t shortcut_length2 = shortcut_length * shortcut_length; - bool forward_is_blocked = false; - bool forward_is_too_far = false; - bool backward_is_blocked = false; - bool backward_is_too_far = false; - while (true) - { - const bool forward_has_converged = forward_is_blocked || forward_is_too_far; - const bool backward_has_converged = backward_is_blocked || backward_is_too_far; - if (forward_has_converged && backward_has_converged) - { - if (forward_is_too_far && backward_is_too_far && vSize2(p0_it.prev().p() - p2_it.next().p()) < shortcut_length2) - { - // o - // / \ . - // o o - // | | - // \ / . - // | | - // \ / . - // | | - // o o - --p0_it; - ++p2_it; - forward_is_too_far = false; // invalidate data - backward_is_too_far = false; // invalidate data - continue; - } - else - { - break; - } - } - smooth_outward_step(p1, shortcut_length2, p0_it, p2_it, forward_is_blocked, backward_is_blocked, forward_is_too_far, backward_is_too_far); - if (p0_it.prev() == p2_it || p0_it == p2_it) - { // stop if we went all the way around the polygon - // this should only be the case for hole polygons (?) - if (forward_is_too_far && backward_is_too_far) - { - // in case p0_it.prev() == p2_it : - // / . - // / /| - // | becomes | | - // \ \| - // \ . - // in case p0_it == p2_it : - // / . - // / becomes /| - // \ \| - // \ . - break; - } - else - { - // this whole polygon can be removed - return true; - } - } - } - - const Point2LL v02 = p2_it.p() - p0_it.p(); - const int64_t v02_size2 = vSize2(v02); - // set the following: - // p0_it = start point of line - // p2_it = end point of line - if (std::abs(v02_size2 - shortcut_length2) < shortcut_length * 10) // i.e. if (size2 < l * (l+10) && size2 > l * (l-10)) - { // v02 is approximately shortcut length - // handle this separately to avoid rounding problems below in the getPointOnLineWithDist function - // p0_it and p2_it are already correct - } - else if (! backward_is_blocked && ! forward_is_blocked) - { // introduce two new points - // 1----b---->2 - // ^ / - // | / - // | / - // |/ - // |a - // | - // 0 - const int64_t v02_size = sqrt(v02_size2); - - const ListPolyIt p0_2_it = p0_it.prev(); - const ListPolyIt p2_2_it = p2_it.next(); - const Point2LL p2_2 = p2_2_it.p(); - const Point2LL p0_2 = p0_2_it.p(); - const Point2LL v02_2 = p0_2 - p2_2; - const int64_t v02_2_size = vSize(v02_2); - double progress - = std::min(1.0, INT2MM(shortcut_length - v02_size) / INT2MM(v02_2_size - v02_size)); // account for rounding error when v02_2_size is approx equal to v02_size - assert(progress >= 0.0f && progress <= 1.0f && "shortcut length must be between last length and new length"); - const Point2LL new_p0 = p0_it.p() + (p0_2 - p0_it.p()) * progress; - p0_it = ListPolyIt::insertPointNonDuplicate(p0_2_it, p0_it, new_p0); - const Point2LL new_p2 = p2_it.p() + (p2_2 - p2_it.p()) * progress; - p2_it = ListPolyIt::insertPointNonDuplicate(p2_it, p2_2_it, new_p2); - } - else if (! backward_is_blocked) - { // forward is blocked, back is open - // | - // 1->b - // ^ : - // | / - // 0 : - // |/ - // |a - // | - // 0_2 - const ListPolyIt p0_2_it = p0_it.prev(); - const Point2LL p0 = p0_it.p(); - const Point2LL p0_2 = p0_2_it.p(); - const Point2LL p2 = p2_it.p(); - Point2LL new_p0; - bool success = LinearAlg2D::getPointOnLineWithDist(p2, p0, p0_2, shortcut_length, new_p0); - // shortcut length must be possible given that last length was ok and new length is too long - if (success) - { -#ifdef ASSERT_INSANE_OUTPUT - assert(new_p0.X < 400000 && new_p0.Y < 400000); -#endif // #ifdef ASSERT_INSANE_OUTPUT - p0_it = ListPolyIt::insertPointNonDuplicate(p0_2_it, p0_it, new_p0); - } - else - { // if not then a rounding error occured - if (vSize(p2 - p0_2) < vSize2(p2 - p0)) - { - p0_it = p0_2_it; // start shortcut at 0 - } - } - } - else if (! forward_is_blocked) - { // backward is blocked, front is open - // 1----2----b----------->2_2 - // ^ ,-' - // | ,-' - //--0.-' - // a - const ListPolyIt p2_2_it = p2_it.next(); - const Point2LL p0 = p0_it.p(); - const Point2LL p2 = p2_it.p(); - const Point2LL p2_2 = p2_2_it.p(); - Point2LL new_p2; - bool success = LinearAlg2D::getPointOnLineWithDist(p0, p2, p2_2, shortcut_length, new_p2); - // shortcut length must be possible given that last length was ok and new length is too long - if (success) - { -#ifdef ASSERT_INSANE_OUTPUT - assert(new_p2.X < 400000 && new_p2.Y < 400000); -#endif // #ifdef ASSERT_INSANE_OUTPUT - p2_it = ListPolyIt::insertPointNonDuplicate(p2_it, p2_2_it, new_p2); - } - else - { // if not then a rounding error occured - if (vSize(p2_2 - p0) < vSize2(p2 - p0)) - { - p2_it = p2_2_it; // start shortcut at 0 - } - } - } - else - { - // | - // __|2 - // | / > shortcut cannot be of the desired length - // ___|/ . - // 0 - // both are blocked and p0_it and p2_it are already correct - } - // delete all cut off points - while (p0_it.next() != p2_it) - { - p0_it.next().remove(); - } - return false; -} - -void ConstPolygonRef::smooth_outward_step( - const Point2LL p1, - const int64_t shortcut_length2, - ListPolyIt& p0_it, - ListPolyIt& p2_it, - bool& forward_is_blocked, - bool& backward_is_blocked, - bool& forward_is_too_far, - bool& backward_is_too_far) -{ - const bool forward_has_converged = forward_is_blocked || forward_is_too_far; - const bool backward_has_converged = backward_is_blocked || backward_is_too_far; - const Point2LL p0 = p0_it.p(); - const Point2LL p2 = p2_it.p(); - bool walk_forward - = ! forward_has_converged && (backward_has_converged || (vSize2(p2 - p1) < vSize2(p0 - p1))); // whether to walk along the p1-p2 direction or in the p1-p0 direction - - if (walk_forward) - { - const ListPolyIt p2_2_it = p2_it.next(); - const Point2LL p2_2 = p2_2_it.p(); - bool p2_is_left = LinearAlg2D::pointIsLeftOfLine(p2, p0, p2_2) >= 0; - if (! p2_is_left) - { - forward_is_blocked = true; - return; - } - - const Point2LL v02_2 = p2_2 - p0_it.p(); - if (vSize2(v02_2) > shortcut_length2) - { - forward_is_too_far = true; - return; - } - - p2_it = p2_2_it; // make one step in the forward direction - backward_is_blocked = false; // invalidate data about backward walking - backward_is_too_far = false; - return; - } - else - { - const ListPolyIt p0_2_it = p0_it.prev(); - const Point2LL p0_2 = p0_2_it.p(); - bool p0_is_left = LinearAlg2D::pointIsLeftOfLine(p0, p0_2, p2) >= 0; - if (! p0_is_left) - { - backward_is_blocked = true; - return; - } - - const Point2LL v02_2 = p2_it.p() - p0_2; - if (vSize2(v02_2) > shortcut_length2) - { - backward_is_too_far = true; - return; - } - - p0_it = p0_2_it; // make one step in the backward direction - forward_is_blocked = false; // invalidate data about forward walking - forward_is_too_far = false; - return; - } -} - -void ConstPolygonRef::smooth_corner_simple( - const Point2LL p0, - const Point2LL p1, - const Point2LL p2, - const ListPolyIt p0_it, - const ListPolyIt p1_it, - const ListPolyIt p2_it, - const Point2LL v10, - const Point2LL v12, - const Point2LL v02, - const int64_t shortcut_length, - double cos_angle) -{ - // 1----b---->2 - // ^ / - // | / - // | / - // |/ - // |a - // | - // 0 - // ideally a1_size == b1_size - if (vSize2(v02) <= shortcut_length * (shortcut_length + 10) // v02 is approximately shortcut length - || (cos_angle > 0.9999 && LinearAlg2D::getDist2FromLine(p2, p0, p1) < 20 * 20)) // p1 is degenerate - { - // handle this separately to avoid rounding problems below in the getPointOnLineWithDist function - p1_it.remove(); - // don't insert new elements - } - else - { - // compute the distance a1 == b1 to get vSize(ab)==shortcut_length with the given angle between v10 and v12 - // 1 - // /|\ . - // / | \ . - // / | \ . - // / | \ . - // a/____|____\b . - // m - // use trigonometry on the right-angled triangle am1 - double a1m_angle = acos(cos_angle) / 2; - const int64_t a1_size = shortcut_length / 2 / sin(a1m_angle); - if (a1_size * a1_size < vSize2(v10) && a1_size * a1_size < vSize2(v12)) - { - Point2LL a = p1 + normal(v10, a1_size); - Point2LL b = p1 + normal(v12, a1_size); -#ifdef ASSERT_INSANE_OUTPUT - assert(vSize(a) < 4000000); - assert(vSize(b) < 4000000); -#endif // #ifdef ASSERT_INSANE_OUTPUT - ListPolyIt::insertPointNonDuplicate(p0_it, p1_it, a); - ListPolyIt::insertPointNonDuplicate(p1_it, p2_it, b); - p1_it.remove(); - } - else if (vSize2(v12) < vSize2(v10)) - { - // b - // 1->2 - // ^ | - // | / - // | | - // |/ - // |a - // | - // 0 - const Point2LL& b = p2_it.p(); - Point2LL a; - bool success = LinearAlg2D::getPointOnLineWithDist(b, p1, p0, shortcut_length, a); - // v02 has to be longer than ab! - if (success) - { // if not success then assume a is negligibly close to 0, but rounding errors caused a problem -#ifdef ASSERT_INSANE_OUTPUT - assert(vSize(a) < 4000000); -#endif // #ifdef ASSERT_INSANE_OUTPUT - ListPolyIt::insertPointNonDuplicate(p0_it, p1_it, a); - } - p1_it.remove(); - } - else - { - // 1---------b----------->2 - // ^ ,-' - // | ,-' - // 0.-' - // a - const Point2LL& a = p0_it.p(); - Point2LL b; - bool success = LinearAlg2D::getPointOnLineWithDist(a, p1, p2, shortcut_length, b); - // v02 has to be longer than ab! - if (success) - { // if not success then assume b is negligibly close to 2, but rounding errors caused a problem -#ifdef ASSERT_INSANE_OUTPUT - assert(vSize(b) < 4000000); -#endif // #ifdef ASSERT_INSANE_OUTPUT - ListPolyIt::insertPointNonDuplicate(p1_it, p2_it, b); - } - p1_it.remove(); - } - } -} - -void ConstPolygonRef::smooth_outward(const AngleDegrees min_angle, int shortcut_length, PolygonRef result) const -{ - // example of smoothed out corner: - // - // 6 - // ^ - // | - // inside | outside - // 2>3>4>5 - // ^ / . - // | / . - // 1 / . - // ^ / . - // |/ . - // | - // | - // 0 - - int shortcut_length2 = shortcut_length * shortcut_length; - double cos_min_angle = cos(min_angle / 180 * std::numbers::pi); - - ListPolygon poly; - ListPolyIt::convertPolygonToList(*this, poly); - - { // remove duplicate vertices - ListPolyIt p1_it(poly, poly.begin()); - do - { - ListPolyIt next = p1_it.next(); - if (vSize2(p1_it.p() - next.p()) < 10 * 10) - { - p1_it.remove(); - } - p1_it = next; - } while (p1_it != ListPolyIt(poly, poly.begin())); - } - - ListPolyIt p1_it(poly, poly.begin()); - do - { - const Point2LL p1 = p1_it.p(); - ListPolyIt p0_it = p1_it.prev(); - ListPolyIt p2_it = p1_it.next(); - const Point2LL p0 = p0_it.p(); - const Point2LL p2 = p2_it.p(); - - const Point2LL v10 = p0 - p1; - const Point2LL v12 = p2 - p1; - double cos_angle = INT2MM(INT2MM(dot(v10, v12))) / vSizeMM(v10) / vSizeMM(v12); - bool is_left_angle = LinearAlg2D::pointIsLeftOfLine(p1, p0, p2) > 0; - if (cos_angle > cos_min_angle && is_left_angle) - { - // angle is so sharp that it can be removed - Point2LL v02 = p2_it.p() - p0_it.p(); - if (vSize2(v02) >= shortcut_length2) - { - smooth_corner_simple(p0, p1, p2, p0_it, p1_it, p2_it, v10, v12, v02, shortcut_length, cos_angle); - } - else - { - bool remove_poly = smooth_corner_complex(p1, p0_it, p2_it, shortcut_length); // edits p0_it and p2_it! - if (remove_poly) - { - // don't convert ListPolygon into result - return; - } - } - // update: - p1_it = p2_it; // next point to consider for whether it's an internal corner - } - else - { - ++p1_it; - } - } while (p1_it != ListPolyIt(poly, poly.begin())); - - ListPolyIt::convertListPolygonToPolygon(poly, result); -} - -Polygons Polygons::smooth_outward(const AngleDegrees max_angle, int shortcut_length) -{ - Polygons ret; - for (unsigned int p = 0; p < size(); p++) - { - PolygonRef poly(paths[p]); - if (poly.size() < 3) - { - continue; - } - if (poly.size() == 3) - { - ret.add(poly); - continue; - } - poly.smooth_outward(max_angle, shortcut_length, ret.newPoly()); - if (ret.back().size() < 3) - { - ret.paths.resize(ret.paths.size() - 1); - } - } - return ret; -} - - -void ConstPolygonRef::splitPolylineIntoSegments(Polygons& result) const -{ - Point2LL last = front(); - for (size_t idx = 1; idx < size(); idx++) - { - Point2LL p = (*this)[idx]; - result.addLine(last, p); - last = p; - } -} - -Polygons ConstPolygonRef::splitPolylineIntoSegments() const -{ - Polygons ret; - splitPolylineIntoSegments(ret); - return ret; -} - -void ConstPolygonRef::splitPolygonIntoSegments(Polygons& result) const -{ - splitPolylineIntoSegments(result); - result.addLine(back(), front()); -} - -Polygons ConstPolygonRef::splitPolygonIntoSegments() const -{ - Polygons ret; - splitPolygonIntoSegments(ret); - return ret; -} - -void ConstPolygonRef::smooth(int remove_length, PolygonRef result) const -{ - // a typical zigzag with the middle part to be removed by removing (1) : - // - // 3 - // ^ - // | - // | - // inside | outside - // 1--->2 - // ^ - // | - // | - // | - // 0 - const ConstPolygonRef& thiss = *path; - ClipperLib::Path* poly = result.path; - if (size() > 0) - { - poly->push_back(thiss[0]); - } - auto is_zigzag = [remove_length](const int64_t v02_size, const int64_t v12_size, const int64_t v13_size, const int64_t dot1, const int64_t dot2) - { - if (v12_size > remove_length) - { // v12 or v13 is too long - return false; - } - const bool p1_is_left_of_v02 = dot1 < 0; - if (! p1_is_left_of_v02) - { // removing p1 wouldn't smooth outward - return false; - } - const bool p2_is_left_of_v13 = dot2 > 0; - if (p2_is_left_of_v13) - { // l0123 doesn't constitute a zigzag ''|,, - return false; - } - if (-dot1 <= v02_size * v12_size / 2) - { // angle at p1 isn't sharp enough - return false; - } - if (-dot2 <= v13_size * v12_size / 2) - { // angle at p2 isn't sharp enough - return false; - } - return true; - }; - Point2LL v02 = thiss[2] - thiss[0]; - Point2LL v02T = turn90CCW(v02); - int64_t v02_size = vSize(v02); - bool force_push = false; - for (unsigned int poly_idx = 1; poly_idx < size(); poly_idx++) - { - const Point2LL& p1 = thiss[poly_idx]; - const Point2LL& p2 = thiss[(poly_idx + 1) % size()]; - const Point2LL& p3 = thiss[(poly_idx + 2) % size()]; - // v02 computed in last iteration - // v02_size as well - const Point2LL v12 = p2 - p1; - const int64_t v12_size = vSize(v12); - const Point2LL v13 = p3 - p1; - const int64_t v13_size = vSize(v13); - - // v02T computed in last iteration - const int64_t dot1 = dot(v02T, v12); - const Point2LL v13T = turn90CCW(v13); - const int64_t dot2 = dot(v13T, v12); - bool push_point = force_push || ! is_zigzag(v02_size, v12_size, v13_size, dot1, dot2); - force_push = false; - if (push_point) - { - poly->push_back(p1); - } - else - { - // do not add the current one to the result - force_push = true; // ensure the next point is added; it cannot also be a zigzag - } - v02T = v13T; - v02 = v13; - v02_size = v13_size; - } -} - -Polygons Polygons::smooth(int remove_length) const -{ - Polygons ret; - for (unsigned int p = 0; p < size(); p++) - { - ConstPolygonRef poly(paths[p]); - if (poly.size() < 3) - { - continue; - } - if (poly.size() == 3) - { - ret.add(poly); - continue; - } - poly.smooth(remove_length, ret.newPoly()); - PolygonRef back = ret.back(); - if (back.size() < 3) - { - back.path->resize(back.path->size() - 1); - } - } - return ret; -} - -void ConstPolygonRef::smooth2(int remove_length, PolygonRef result) const -{ - const ConstPolygonRef& thiss = *this; - ClipperLib::Path* poly = result.path; - if (thiss.size() > 0) - { - poly->push_back(thiss[0]); - } - for (unsigned int poly_idx = 1; poly_idx < thiss.size(); poly_idx++) - { - const Point2LL& last = thiss[poly_idx - 1]; - const Point2LL& now = thiss[poly_idx]; - const Point2LL& next = thiss[(poly_idx + 1) % thiss.size()]; - if (shorterThen(last - now, remove_length) && shorterThen(now - next, remove_length)) - { - poly_idx++; // skip the next line piece (dont escalate the removal of edges) - if (poly_idx < thiss.size()) - { - poly->push_back(thiss[poly_idx]); - } - } - else - { - poly->push_back(thiss[poly_idx]); - } - } -} - -Polygons Polygons::smooth2(int remove_length, int min_area) const -{ - Polygons ret; - for (unsigned int p = 0; p < size(); p++) - { - ConstPolygonRef poly(paths[p]); - if (poly.size() == 0) - { - continue; - } - if (poly.area() < min_area || poly.size() <= 5) // when optimally removing, a poly with 5 pieces results in a triangle. Smaller polys dont have area! - { - ret.add(poly); - continue; - } - if (poly.size() < 4) - { - ret.add(poly); - } - else - { - poly.smooth2(remove_length, ret.newPoly()); - } - } - return ret; -} - -double Polygons::area() const -{ - double area = 0.0; - for (unsigned int poly_idx = 0; poly_idx < size(); poly_idx++) - { - area += operator[](poly_idx).area(); - // note: holes already have negative area - } - return area; -} - -std::vector Polygons::splitIntoParts(bool unionAll) const -{ - std::vector ret; - ClipperLib::Clipper clipper(clipper_init); - ClipperLib::PolyTree resultPolyTree; - clipper.AddPaths(paths, ClipperLib::ptSubject, true); - if (unionAll) - clipper.Execute(ClipperLib::ctUnion, resultPolyTree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - else - clipper.Execute(ClipperLib::ctUnion, resultPolyTree); - - splitIntoParts_processPolyTreeNode(&resultPolyTree, ret); - return ret; -} - -void Polygons::splitIntoParts_processPolyTreeNode(ClipperLib::PolyNode* node, std::vector& ret) const -{ - for (int n = 0; n < node->ChildCount(); n++) - { - ClipperLib::PolyNode* child = node->Childs[n]; - PolygonsPart part; - part.add(child->Contour); - for (int i = 0; i < child->ChildCount(); i++) - { - part.add(child->Childs[i]->Contour); - splitIntoParts_processPolyTreeNode(child->Childs[i], ret); - } - ret.push_back(part); - } -} - -std::vector Polygons::sortByNesting() const -{ - std::vector ret; - ClipperLib::Clipper clipper(clipper_init); - ClipperLib::PolyTree resultPolyTree; - clipper.AddPaths(paths, ClipperLib::ptSubject, true); - clipper.Execute(ClipperLib::ctUnion, resultPolyTree); - - sortByNesting_processPolyTreeNode(&resultPolyTree, 0, ret); - return ret; -} - -void Polygons::sortByNesting_processPolyTreeNode(ClipperLib::PolyNode* node, const size_t nesting_idx, std::vector& ret) const -{ - for (int n = 0; n < node->ChildCount(); n++) - { - ClipperLib::PolyNode* child = node->Childs[n]; - if (nesting_idx >= ret.size()) - { - ret.resize(nesting_idx + 1); - } - ret[nesting_idx].add(child->Contour); - sortByNesting_processPolyTreeNode(child, nesting_idx + 1, ret); - } -} - -Polygons Polygons::tubeShape(const coord_t inner_offset, const coord_t outer_offset) const -{ - return this->offset(outer_offset).difference(this->offset(-inner_offset)); -} - -Polygons Polygons::removeNearSelfIntersections() const -{ - using map_pt = mapbox::geometry::point; - using map_ring = mapbox::geometry::linear_ring; - using map_poly = mapbox::geometry::polygon; - using map_mpoly = mapbox::geometry::multi_polygon; - - map_mpoly mwpoly; - - mapbox::geometry::wagyu::wagyu wagyu; - - for (auto& polygon : splitIntoParts()) - { - mwpoly.emplace_back(); - map_poly& wpoly = mwpoly.back(); - for (auto& path : polygon) - { - wpoly.push_back(std::move(*reinterpret_cast>*>(&path))); - for (auto& point : wpoly.back()) - { - point.x /= 4; - point.y /= 4; - } - wagyu.add_ring(wpoly.back()); - } - } - - map_mpoly sln; - - wagyu.execute(mapbox::geometry::wagyu::clip_type_union, sln, mapbox::geometry::wagyu::fill_type_even_odd, mapbox::geometry::wagyu::fill_type_even_odd); - - Polygons polys; - - for (auto& poly : sln) - { - for (auto& ring : poly) - { - ring.pop_back(); - for (auto& point : ring) - { - point.x *= 4; - point.y *= 4; - } - polys.add(*reinterpret_cast*>(&ring)); // NOTE: 'add' already moves the vector - } - } - polys = polys.unionPolygons(); - polys.removeColinearEdges(); - - return polys; -} - -size_t PartsView::getPartContaining(size_t poly_idx, size_t* boundary_poly_idx) const -{ - const PartsView& partsView = *this; - for (size_t part_idx_now = 0; part_idx_now < partsView.size(); part_idx_now++) - { - const std::vector& partView = partsView[part_idx_now]; - if (partView.size() == 0) - { - continue; - } - std::vector::const_iterator result = std::find(partView.begin(), partView.end(), poly_idx); - if (result != partView.end()) - { - if (boundary_poly_idx) - { - *boundary_poly_idx = partView[0]; - } - return part_idx_now; - } - } - return NO_INDEX; -} - -PolygonsPart PartsView::assemblePart(size_t part_idx) const -{ - const PartsView& partsView = *this; - PolygonsPart ret; - if (part_idx != NO_INDEX) - { - for (size_t poly_idx_ff : partsView[part_idx]) - { - ret.add(polygons_[poly_idx_ff]); - } - } - return ret; -} - -PolygonsPart PartsView::assemblePartContaining(size_t poly_idx, size_t* boundary_poly_idx) const -{ - PolygonsPart ret; - size_t part_idx = getPartContaining(poly_idx, boundary_poly_idx); - if (part_idx != NO_INDEX) - { - return assemblePart(part_idx); - } - return ret; -} - -PartsView Polygons::splitIntoPartsView(bool unionAll) -{ - Polygons reordered; - PartsView partsView(*this); - ClipperLib::Clipper clipper(clipper_init); - ClipperLib::PolyTree resultPolyTree; - clipper.AddPaths(paths, ClipperLib::ptSubject, true); - if (unionAll) - clipper.Execute(ClipperLib::ctUnion, resultPolyTree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - else - clipper.Execute(ClipperLib::ctUnion, resultPolyTree); - - splitIntoPartsView_processPolyTreeNode(partsView, reordered, &resultPolyTree); - - (*this) = reordered; - return partsView; -} - -void Polygons::splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Polygons& reordered, ClipperLib::PolyNode* node) const -{ - for (int n = 0; n < node->ChildCount(); n++) - { - ClipperLib::PolyNode* child = node->Childs[n]; - partsView.emplace_back(); - size_t pos = partsView.size() - 1; - partsView[pos].push_back(reordered.size()); - reordered.add(child->Contour); // TODO: should this steal the internal representation for speed? - for (int i = 0; i < child->ChildCount(); i++) - { - partsView[pos].push_back(reordered.size()); - reordered.add(child->Childs[i]->Contour); - splitIntoPartsView_processPolyTreeNode(partsView, reordered, child->Childs[i]); - } - } -} - -void Polygons::ensureManifold() -{ - const Polygons& polys = *this; - std::vector duplicate_locations; - std::unordered_set poly_locations; - for (size_t poly_idx = 0; poly_idx < polys.size(); poly_idx++) - { - ConstPolygonRef poly = polys[poly_idx]; - for (size_t point_idx = 0; point_idx < poly.size(); point_idx++) - { - Point2LL p = poly[point_idx]; - if (poly_locations.find(p) != poly_locations.end()) - { - duplicate_locations.push_back(p); - } - poly_locations.emplace(p); - } - } - Polygons removal_dots; - for (Point2LL p : duplicate_locations) - { - PolygonRef dot = removal_dots.newPoly(); - dot.add(p + Point2LL(0, 5)); - dot.add(p + Point2LL(5, 0)); - dot.add(p + Point2LL(0, -5)); - dot.add(p + Point2LL(-5, 0)); - } - if (! removal_dots.empty()) - { - *this = polys.difference(removal_dots); - } -} - -} // namespace cura diff --git a/tests/InfillTest.cpp b/tests/InfillTest.cpp index 157404288f..8f9866e9ee 100644 --- a/tests/InfillTest.cpp +++ b/tests/InfillTest.cpp @@ -290,7 +290,7 @@ TEST_P(InfillTest, TestInfillSanity) Shape result_polygon_lines = params.result_polygons; for (Polygon& poly : result_polygon_lines) { - poly.add(poly.front()); + poly.push_back(poly.front()); } ASSERT_LE(std::abs(padded_shape_outline.intersectionPolyLines(result_polygon_lines, restitch).polyLineLength() - result_polygon_lines.polyLineLength()), maximum_error) << "Infill (lines) should not be outside target polygon."; diff --git a/tests/LayerPlanTest.cpp b/tests/LayerPlanTest.cpp index 3ca48893da..7c7afa13e7 100644 --- a/tests/LayerPlanTest.cpp +++ b/tests/LayerPlanTest.cpp @@ -320,30 +320,30 @@ class AddTravelTest : public LayerPlanTest, public testing::WithParamInterface("false", "false", "off", false, false, AddTravelTestScene::OPEN)) { - around_start_end.add(Point2LL(-100, -100)); - around_start_end.add(Point2LL(500100, -100)); - around_start_end.add(Point2LL(500100, 500100)); - around_start_end.add(Point2LL(-100, 500100)); - - around_start.add(Point2LL(-100, -100)); - around_start.add(Point2LL(100, -100)); - around_start.add(Point2LL(100, 100)); - around_start.add(Point2LL(-100, 100)); - - around_end.add(Point2LL(249900, 249900)); - around_end.add(Point2LL(250100, 249900)); - around_end.add(Point2LL(250100, 250100)); - around_end.add(Point2LL(249900, 249900)); - - between.add(Point2LL(250000, 240000)); - between.add(Point2LL(260000, 240000)); - between.add(Point2LL(260000, 300000)); - between.add(Point2LL(250000, 300000)); - - between_hole.add(Point2LL(250000, 240000)); - between_hole.add(Point2LL(250000, 300000)); - between_hole.add(Point2LL(260000, 300000)); - between_hole.add(Point2LL(260000, 240000)); + around_start_end.push_back(Point2LL(-100, -100)); + around_start_end.push_back(Point2LL(500100, -100)); + around_start_end.push_back(Point2LL(500100, 500100)); + around_start_end.push_back(Point2LL(-100, 500100)); + + around_start.push_back(Point2LL(-100, -100)); + around_start.push_back(Point2LL(100, -100)); + around_start.push_back(Point2LL(100, 100)); + around_start.push_back(Point2LL(-100, 100)); + + around_end.push_back(Point2LL(249900, 249900)); + around_end.push_back(Point2LL(250100, 249900)); + around_end.push_back(Point2LL(250100, 250100)); + around_end.push_back(Point2LL(249900, 249900)); + + between.push_back(Point2LL(250000, 240000)); + between.push_back(Point2LL(260000, 240000)); + between.push_back(Point2LL(260000, 300000)); + between.push_back(Point2LL(250000, 300000)); + + between_hole.push_back(Point2LL(250000, 240000)); + between_hole.push_back(Point2LL(250000, 300000)); + between_hole.push_back(Point2LL(260000, 300000)); + between_hole.push_back(Point2LL(260000, 240000)); } /*! @@ -371,24 +371,24 @@ class AddTravelTest : public LayerPlanTest, public testing::WithParamInterface + #include +#include + +#include "geometry/shape.h" + namespace cura { // NOLINTBEGIN(*-magic-numbers) @@ -161,4 +164,4 @@ TEST(AABBTest, TestToPolygon) } // NOLINTEND(*-magic-numbers) -} // namespace cura \ No newline at end of file +} // namespace cura