diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 1267e216c1..6159b2c5c3 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -145,6 +145,8 @@ add_library(libslic3r STATIC Point.hpp Polygon.cpp Polygon.hpp + MutablePolygon.cpp + MutablePolygon.hpp PolygonTrimmer.cpp PolygonTrimmer.hpp Polyline.cpp diff --git a/src/libslic3r/MutablePolygon.cpp b/src/libslic3r/MutablePolygon.cpp new file mode 100644 index 0000000000..951485259d --- /dev/null +++ b/src/libslic3r/MutablePolygon.cpp @@ -0,0 +1,242 @@ +#include "MutablePolygon.hpp" +#include "Line.hpp" + +namespace Slic3r { + +// Remove exact duplicate points. May reduce the polygon down to empty polygon. +void remove_duplicates(MutablePolygon &polygon) +{ + if (! polygon.empty()) { + auto begin = polygon.begin(); + auto it = begin; + for (++ it; it != begin;) { + auto prev = it.prev(); + if (*prev == *it) + it = it.remove(); + else + ++ it; + } + } +} + +// Remove nearly duplicate points. May reduce the polygon down to empty polygon. +void remove_duplicates(MutablePolygon &polygon, double eps) +{ + if (! polygon.empty()) { + auto eps2 = eps * eps; + auto begin = polygon.begin(); + auto it = begin; + for (++ it; it != begin;) { + auto prev = it.prev(); + if ((*it - *prev).cast().squaredNorm() < eps2) + it = it.remove(); + else + ++ it; + } + } +} + +// Sample a point on line (a, b) at distance "dist" from ref_pt. +// If two points fulfill the condition, then the first one (closer to point a) is taken. +// If none of the two points falls on line (a, b), return false. +template +static inline VectorType point_on_line_at_dist(const VectorType &a, const VectorType &b, const VectorType &ref_pt, const double dist) +{ + using T = typename VectorType::Scalar; + auto v = b - a; + auto l2 = v.squaredNorm(); + assert(l2 > T(0)); + auto vpt = ref_pt - a; + // Parameter of the foot point of ref_pt on line (a, b). + auto t = v.dot(vpt) / l2; + // Foot point of ref_pt on line (a, b). + auto foot_pt = a + t * v; + auto dfoot2 = vpt.squaredNorm() - (foot_pt - ref_pt).squaredNorm(); + // Distance of the result point from the foot point, normalized to length of (a, b). + auto dfoot = dfoot2 > T(0) ? sqrt(dfoot2) / sqrt(l2) : T(0); + auto t_result = t - dfoot; + if (t_result < T(0)) + t_result = t + dfoot; + t_result = Slic3r::clamp(0., 1., t_result); + return a + v * t; +} + +static bool smooth_corner_complex(const Vec2d p1, MutablePolygon::iterator &it0, MutablePolygon::iterator &it2, const double 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 + double 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; + for (;;) { + 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 && (*it0.prev() - *it2.next()).cast().squaredNorm() < shortcut_length2) { + // Trim the narrowing region. + -- it0; + ++ it2; + forward_is_too_far = false; + backward_is_too_far = false; + continue; + } else + break; + } + + const Vec2d p0 = it0->cast(); + const Vec2d p2 = it2->cast(); + if (! forward_has_converged && (backward_has_converged || (p2 - p1).squaredNorm() < (p0 - p1).squaredNorm())) { + // walk forward + const auto it2_2 = it2.next(); + const Vec2d p2_2 = it2_2->cast(); + if (cross2(p2 - p0, p2_2 - p0) > 0) { + forward_is_blocked = true; + } else if ((p2_2 - p0).squaredNorm() > shortcut_length2) { + forward_is_too_far = true; + } else { + it2 = it2_2; // make one step in the forward direction + backward_is_blocked = false; // invalidate data about backward walking + backward_is_too_far = false; + } + } else { + // walk backward + const auto it0_2 = it0.prev(); + const Vec2d p0_2 = it0_2->cast(); + if (cross2(p0_2 - p0, p2 - p0_2) > 0) { + backward_is_blocked = true; + } else if ((p2 - p0_2).squaredNorm() > shortcut_length2) { + backward_is_too_far = true; + } else { + it0 = it0_2; // make one step in the backward direction + forward_is_blocked = false; // invalidate data about forward walking + forward_is_too_far = false; + } + } + + if (it0.prev() == it2 || it0 == it2) { + // 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 Vec2d p0 = it0->cast(); + const Vec2d p2 = it2->cast(); + const Vec2d v02 = p2 - p0; + const int64_t l2_v02 = v02.squaredNorm(); + if (std::abs(l2_v02 - 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) { + const auto l_v02 = sqrt(l2_v02); + const Vec2d p0_2 = it0.prev()->cast(); + const Vec2d p2_2 = it2.next()->cast(); + double t = Slic3r::clamp(0., 1., (shortcut_length - l_v02) / ((p2_2 - p0_2).norm() - l_v02)); + it0 = it0.prev().insert((p0 + (p0_2 - p0) * t).cast()); + it2 = it2.insert((p2 + (p2_2 - p2) * t).cast()); + } else if (! backward_is_blocked) { + it0 = it0.prev().insert(point_on_line_at_dist(p0, Vec2d(it0.prev()->cast()), p2, shortcut_length).cast()); + } else if (! forward_is_blocked) { + it2 = it2.insert(point_on_line_at_dist(p2, Vec2d(it2.next()->cast()), p0, shortcut_length).cast()); + } else { + // | + // __|2 + // | / > shortcut cannot be of the desired length + // ___|/ . + // 0 + // both are blocked and p0_it and p2_it are already correct + } + // Delete all the points between it0 and it2. + while (it0.next() != it2) + it0.next().remove(); + return false; +} + +void smooth_outward(MutablePolygon &polygon, double shortcut_length) +{ + remove_duplicates(polygon, scaled(0.01)); + + const int shortcut_length2 = shortcut_length * shortcut_length; + static constexpr const double cos_min_angle = -0.70710678118654752440084436210485; // cos(135 degrees) + + MutablePolygon::iterator it1 = polygon.begin(); + do { + const Vec2d p1 = it1->cast(); + auto it0 = it1.prev(); + auto it2 = it1.next(); + const Vec2d p0 = it0->cast(); + const Vec2d p2 = it2->cast(); + const Vec2d v1 = p0 - p1; + const Vec2d v2 = p2 - p1; + const double cos_angle = v1.dot(v2); + if (cos_angle < cos_min_angle && cross2(v1, v2) < 0) { + // Simplify the sharp angle. + const Vec2d v02 = p2 - p0; + const double l2_v02 = v02.squaredNorm(); + if (l2_v02 >= shortcut_length2) { + // Trim an obtuse corner. + it1.remove(); + if (l2_v02 > Slic3r::sqr(shortcut_length + SCALED_EPSILON)) { + double l2_1 = v1.squaredNorm(); + double l2_2 = v2.squaredNorm(); + bool trim = true; + if (cos_angle > 0.9999) { + // The triangle p0, p1, p2 is likely degenerate. + // Measure height of the triangle. + double d2 = l2_1 > l2_2 ? line_alg::distance_to_squared(Linef{ p0, p1 }, p2) : line_alg::distance_to_squared(Linef{ p2, p1 }, p0); + if (d2 < Slic3r::sqr(scaled(0.02))) + trim = false; + } + if (trim) { + Vec2d bisector = v1 / l2_1 + v2 / l2_2; + double d1 = v1.dot(bisector) / l2_1; + double d2 = v2.dot(bisector) / l2_2; + double lbisector = bisector.norm(); + if (d1 < shortcut_length && d2 < shortcut_length) { + it0.insert((p1 + v1 * (shortcut_length / d1)).cast()) + .insert((p1 + v2 * (shortcut_length / d2)).cast()); + } else if (v1.squaredNorm() < v2.squaredNorm()) + it0.insert(point_on_line_at_dist(p1, p2, p0, shortcut_length).cast()); + else + it0.insert(point_on_line_at_dist(p1, p0, p2, shortcut_length).cast()); + } + } + } else { + bool remove_poly = smooth_corner_complex(p1, it0, it2, shortcut_length); // edits p0_it and p2_it! + if (remove_poly) { + // don't convert ListPolygon into result + return; + } + } + // update: + it1 = it2; // next point to consider for whether it's an internal corner + } + else + ++ it1; + } while (it1 != polygon.begin()); +} + +} // namespace Slic3r diff --git a/src/libslic3r/MutablePolygon.hpp b/src/libslic3r/MutablePolygon.hpp new file mode 100644 index 0000000000..b699a25eda --- /dev/null +++ b/src/libslic3r/MutablePolygon.hpp @@ -0,0 +1,227 @@ +#ifndef slic3r_MutablePolygon_hpp_ +#define slic3r_MutablePolygon_hpp_ + +#include "Point.hpp" +#include "Polygon.hpp" + +namespace Slic3r { + +class MutablePolygon +{ +public: + using IndexType = int32_t; + using PointType = Point; + class const_iterator { + public: + bool operator==(const const_iterator &rhs) const { assert(m_data == rhs.m_data); assert(this->valid()); return m_idx == rhs.m_idx; } + bool operator!=(const const_iterator &rhs) const { return ! (*this == rhs); } + const_iterator& operator--() { assert(this->valid()); m_idx = m_data->at(m_idx).prev; return *this; } + const_iterator operator--(int) { const_iterator result(*this); --(*this); return result; } + const_iterator& operator++() { assert(this->valid()); m_idx = m_data->at(m_idx).next; return *this; } + const_iterator operator++(int) { const_iterator result(*this); ++(*this); return result; } + const_iterator prev() const { assert(this->valid()); return { m_data, m_data->at(m_idx).prev }; } + const_iterator next() const { assert(this->valid()); return { m_data, m_data->at(m_idx).next }; } + bool valid() const { return m_idx >= 0; } + const PointType& operator*() const { return m_data->at(m_idx).point; } + const PointType* operator->() const { return &m_data->at(m_idx).point; } + const MutablePolygon& polygon() const { assert(this->valid()); m_data; } + IndexType size() const { assert(this->valid()); m_data->size(); } + private: + const_iterator(const MutablePolygon *data, IndexType idx) : m_data(data), m_idx(idx) {} + friend class MutablePolygon; + const MutablePolygon *m_data; + IndexType m_idx; + }; + + class iterator { + public: + bool operator==(const iterator &rhs) const { assert(m_data == rhs.m_data); assert(this->valid()); return m_idx == rhs.m_idx; } + bool operator!=(const iterator &rhs) const { return !(*this == rhs); } + iterator& operator--() { assert(this->valid()); m_idx = m_data->at(m_idx).prev; return *this; } + iterator operator--(int) { iterator result(*this); --(*this); return result; } + iterator& operator++() { assert(this->valid()); m_idx = m_data->at(m_idx).next; return *this; } + iterator operator++(int) { iterator result(*this); ++(*this); return result; } + iterator prev() const { assert(this->valid()); return { m_data, m_data->at(m_idx).prev }; } + iterator next() const { assert(this->valid()); return { m_data, m_data->at(m_idx).next }; } + bool valid() const { return m_idx >= 0; } + PointType& operator*() const { return m_data->at(m_idx).point; } + PointType* operator->() const { return &m_data->at(m_idx).point; } + MutablePolygon& polygon() const { assert(this->valid()); m_data; } + IndexType size() const { assert(this->valid()); m_data->size(); } + iterator& remove() { this->m_idx = m_data->remove(*this).m_idx; return *this; } + iterator insert(const PointType pt) const { return m_data->insert(*this, pt); } + private: + iterator(MutablePolygon *data, IndexType idx) : m_data(data), m_idx(idx) {} + friend class MutablePolygon; + MutablePolygon *m_data; + IndexType m_idx; + }; + + MutablePolygon() = default; + MutablePolygon(const Polygon &rhs, size_t reserve = 0) : MutablePolygon(rhs.points.begin(), rhs.points.end(), reserve) {} + MutablePolygon(std::initializer_list rhs, size_t reserve = 0) : MutablePolygon(rhs.begin(), rhs.end(), reserve) {} + + template + MutablePolygon(IT begin, IT end, size_t reserve = 0) { + m_size = IndexType(end - begin); + if (m_size > 0) { + m_head = 0; + m_data.reserve(std::max(m_size, reserve)); + auto i = IndexType(-1); + auto j = IndexType(1); + for (auto it = begin; it != end; ++ it) + m_data.push_back({ *it, i ++, j ++ }); + m_data.front().prev = m_size - 1; + m_data.back ().next = 0; + } + }; + + Polygon polygon() const { + Polygon out; + if (this->valid()) { + out.points.reserve(this->size()); + for (auto it = this->cbegin(); it != this->cend(); ++ it) + out.points.emplace_back(*it); + } + return out; + }; + + bool empty() const { return this->m_size == 0; } + size_t size() const { return this->m_size; } + size_t capacity() const { return this->m_data.capacity(); } + bool valid() const { return this->m_size >= 3; } + + iterator begin() { return { this, m_head }; } + const_iterator cbegin() const { return { this, m_head }; } + const_iterator begin() const { return this->cbegin(); } + // End points to the last item before roll over. This is different from the usual end() concept! + iterator end() { return { this, this->empty() ? -1 : this->at(m_head).prev }; } + const_iterator cend() const { return { this, this->empty() ? -1 : this->at(m_head).prev }; } + const_iterator end() const { return this->cend(); } + + // Returns iterator following the removed element. Returned iterator will become invalid if last point is removed. + // If begin() is removed, then the next element will become the new begin(). + iterator remove(const iterator it) { assert(it.m_data == this); return { this, this->remove(it.m_idx) }; } + // Insert a new point before it. Returns iterator to the newly inserted point. + // begin() will not change, end() may point to the newly inserted point. + iterator insert(const iterator it, const PointType pt) { assert(it.m_data == this); return { this, this->insert(it.m_idx, pt) }; } + +private: + struct LinkedPoint { + PointType point; + IndexType prev; + IndexType next; + }; + std::vector m_data; + // Number of points in the linked list. + IndexType m_size { 0 }; + IndexType m_head { IndexType(-1) }; + // Head of the free list. + IndexType m_head_free { IndexType(-1) }; + + LinkedPoint& at(IndexType i) { return m_data[i]; } + const LinkedPoint& at(IndexType i) const { return m_data[i]; } + + IndexType remove(const IndexType i) { + assert(i >= 0); + assert(m_size > 0); + assert(m_head != -1); + LinkedPoint &lp = this->at(i); + IndexType prev = lp.prev; + IndexType next = lp.next; + lp.next = m_head_free; + m_head_free = i; + if (-- m_size == 0) + m_head = -1; + else if (m_head == i) + m_head = next; + assert(! this->empty() || (prev == i && next == i)); + if (this->empty()) + return IndexType(-1); + this->at(prev).next = next; + this->at(next).prev = prev; + return next; + } + + IndexType insert(const IndexType i, const Point pt) { + assert(i >= 0); + IndexType n; + IndexType j = this->at(i).prev; + if (m_head_free == -1) { + // Allocate a new item. + n = IndexType(m_data.size()); + m_data.push_back({ pt, j, i }); + } else { + n = m_head_free; + LinkedPoint &nlp = this->at(n); + m_head_free = nlp.next; + nlp = { pt, j, i }; + } + this->at(j).next = n; + this->at(i).prev = n; + ++ m_size; + return n; + } + + /* + IndexType insert(const IndexType i, const Point pt) { + assert(i >= 0); + if (this->at(i).point == pt) + return i; + IndexType j = this->at(i).next; + if (this->at(j).point == pt) + return i; + IndexType n; + if (m_head_free == -1) { + // Allocate a new item. + n = IndexType(m_data.size()); + m_data.push_back({ pt, i, j }); + } else { + LinkedPoint &nlp = this->at(m_head_free); + m_head_free = nlp.next; + nlp = { pt, i, j }; + } + this->at(i).next = n; + this->at(j).prev = n; + ++ m_size; + return n; + } + */ +}; + +inline bool operator==(const MutablePolygon &p1, const MutablePolygon &p2) +{ + if (p1.size() != p2.size()) + return false; + if (p1.empty()) + return true; + auto begin = p1.cbegin(); + auto it = begin; + auto it2 = p2.cbegin(); + for (;;) { + if (! (*it == *it2)) + return false; + if (++ it == begin) + return true; + ++ it2; + } +} + +inline bool operator!=(const MutablePolygon &p1, const MutablePolygon &p2) { return ! (p1 == p2); } + +// Remove exact duplicate points. May reduce the polygon down to empty polygon. +void remove_duplicates(MutablePolygon &polygon); +void remove_duplicates(MutablePolygon &polygon, double eps); + +void smooth_outward(MutablePolygon &polygon, double shortcut_length); + +inline Polygon smooth_outward(const Polygon &polygon, double shortcut_length) +{ + MutablePolygon mp(polygon, polygon.size() * 2); + smooth_outward(mp, shortcut_length); + return mp.polygon(); +} + +} + +#endif // slic3r_MutablePolygon_hpp_ diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index d11af8b58c..84b4a825da 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -56,11 +56,21 @@ typedef Eigen::Transform Transform3d inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0) || (lhs(0) == rhs(0) && lhs(1) < rhs(1)); } -// One likely does not want to perform the cross product with a 32bit accumulator. -//inline int32_t cross2(const Vec2i32 &v1, const Vec2i32 &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } -inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } -inline float cross2(const Vec2f &v1, const Vec2f &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } -inline double cross2(const Vec2d &v1, const Vec2d &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } +template +int32_t cross2(const Eigen::MatrixBase> &v1, const Eigen::MatrixBase> &v2) = delete; + +template +inline T cross2(const Eigen::MatrixBase> &v1, const Eigen::MatrixBase> &v2) +{ + return v1(0) * v2(1) - v1(1) * v2(0); +} + +template +inline typename Derived::Scalar cross2(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) +{ + static_assert(std::is_same::value, "cross2(): Scalar types of 1st and 2nd operand must be equal."); + return v1(0) * v2(1) - v1(1) * v2(0); +} template inline Eigen::Matrix perp(const Eigen::MatrixBase> &v) { return Eigen::Matrix(- v.y(), v.x()); } diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 501af0c6f3..f93cf10afd 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -11,6 +11,7 @@ add_executable(${_TEST_NAME}_tests test_geometry.cpp test_placeholder_parser.cpp test_polygon.cpp + test_mutable_polygon.cpp test_stl.cpp test_meshsimplify.cpp test_meshboolean.cpp diff --git a/tests/libslic3r/test_mutable_polygon.cpp b/tests/libslic3r/test_mutable_polygon.cpp new file mode 100644 index 0000000000..2214da6ef0 --- /dev/null +++ b/tests/libslic3r/test_mutable_polygon.cpp @@ -0,0 +1,145 @@ +#include + +#include "libslic3r/MutablePolygon.hpp" + +using namespace Slic3r; + +SCENARIO("Iterators", "[MutablePolygon]") { + GIVEN("Polygon with three points") { + Slic3r::MutablePolygon p({ { 0, 0 }, { 0, 1 }, { 1, 0 } }); + WHEN("Iterating upwards") { + auto begin = p.begin(); + auto end = p.end(); + auto it = begin; + THEN("++ it is not equal to begin") { + REQUIRE(++ it != begin); + } THEN("++ it is not equal to end") { + REQUIRE(++ it != end); + } THEN("++ (++ it) is not equal to begin") { + REQUIRE(++ (++ it) != begin); + } THEN("++ (++ it) is equal to end") { + REQUIRE(++ (++ it) == end); + } THEN("++ (++ (++ it)) is equal to begin") { + REQUIRE(++ (++ (++ it)) == begin); + } THEN("++ (++ (++ it)) is not equal to end") { + REQUIRE(++ (++ (++ it)) != end); + } + } + WHEN("Iterating downwards") { + auto begin = p.begin(); + auto end = p.end(); + auto it = begin; + THEN("-- it is not equal to begin") { + REQUIRE(-- it != begin); + } THEN("-- it is equal to end") { + REQUIRE(-- it == end); + } THEN("-- (-- it) is not equal to begin") { + REQUIRE(-- (-- it) != begin); + } THEN("-- (-- it) is not equal to end") { + REQUIRE(-- (-- it) != end); + } THEN("-- (-- (-- it)) is equal to begin") { + REQUIRE(-- (-- (-- it)) == begin); + } THEN("-- (-- (-- it)) is not equal to end") { + REQUIRE(-- (-- (-- it)) != end); + } + } + WHEN("Deleting 1st point") { + auto it_2nd = p.begin().next(); + auto it_3rd = p.end(); + auto it = p.begin().remove(); + THEN("Size is 2") { + REQUIRE(p.size() == 2); + } THEN("p.begin().remove() == it_2nd") { + REQUIRE(it == it_2nd); + } THEN("it_2nd == new begin()") { + REQUIRE(it_2nd == p.begin()); + } + } + WHEN("Deleting 2nd point") { + auto it_1st = p.begin(); + auto it_2nd = it_1st.next(); + auto it_3rd = p.end(); + auto it = it_2nd.remove(); + THEN("Size is 2") { + REQUIRE(p.size() == 2); + REQUIRE(! p.empty()); + } THEN("it_2nd.remove() == it_3rd") { + REQUIRE(it == it_2nd); + } THEN("it_1st == new begin()") { + REQUIRE(it_1st == p.begin()); + } + } + WHEN("Deleting two points") { + p.begin().remove().remove(); + THEN("Size is 1") { + REQUIRE(p.size() == 1); + } THEN("p.begin().next() == p.begin()") { + REQUIRE(p.begin().next() == p.begin()); + } THEN("p.begin().prev() == p.begin()") { + REQUIRE(p.begin().prev() == p.begin()); + } + } + WHEN("Deleting all points") { + auto it = p.begin().remove().remove().remove(); + THEN("Size is 0") { + REQUIRE(p.size() == 0); + REQUIRE(p.empty()); + } THEN("! p.begin().valid()") { + REQUIRE(!p.begin().valid()); + } THEN("last iterator not valid") { + REQUIRE(! it.valid()); + } + } + WHEN("Inserting a point at the beginning") { + p.insert(p.begin(), { 3, 4 }); + THEN("Polygon content is ok") { + REQUIRE(p == MutablePolygon{ { 0, 0 }, { 0, 1 }, { 1, 0 }, { 3, 4 } }); + } + } + WHEN("Inserting a point at the 2nd position") { + p.insert(++ p.begin(), { 3, 4 }); + THEN("Polygon content is ok") { + REQUIRE(p == MutablePolygon{ { 0, 0 }, { 3, 4 }, { 0, 1 }, { 1, 0 } }); + } + } WHEN("Inserting a point after a point was removed") { + size_t capacity = p.capacity(); + THEN("Initial capacity is 3") { + REQUIRE(capacity == 3); + } + p.begin().remove(); + THEN("After removal of the 1st point the capacity is still 3") { + REQUIRE(p.capacity() == 3); + } + THEN("After removal of the 1st point the content is ok") { + REQUIRE(p == MutablePolygon{ { 0, 1 }, { 1, 0 } }); + } + p.insert(p.begin(), { 5, 6 }); + THEN("After insertion at head position the polygon content is ok") { + REQUIRE(p == MutablePolygon{ { 0, 1 }, { 1, 0 }, { 5, 6 } }); + } THEN("and the capacity is still 3") { + REQUIRE(p.capacity() == 3); + } + } + } +} + +SCENARIO("Remove degenerate points from MutablePolygon", "[MutablePolygon]") { + GIVEN("Polygon with duplicate points"){ + Slic3r::MutablePolygon p({ + { 0, 0 }, + { 0, 100 }, { 0, 100 }, { 0, 100 }, + { 0, 150 }, + { 0, 200 }, + { 200, 200 }, + { 180, 200 }, { 180, 200 }, + { 180, 20 }, + { 180, 0 }, + }); + WHEN("Duplicate points are removed") { + remove_duplicates(p); + THEN("Polygon content is ok") { + REQUIRE(p == Slic3r::MutablePolygon{ { 0, 0 }, { 0, 100 }, { 0, 150 }, { 0, 200 }, { 200, 200 }, { 180, 200 }, { 180, 20 }, { 180, 0 } }); + } + } + } +}