mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-14 10:17:55 -06:00
WIP: MutablePolygon - linked list based polygon implementation
allowing rapid insertion and removal of points. WIP: porting smooth_outward() from Cura.
This commit is contained in:
parent
409849d238
commit
5276bd98d7
6 changed files with 632 additions and 5 deletions
|
@ -145,6 +145,8 @@ add_library(libslic3r STATIC
|
||||||
Point.hpp
|
Point.hpp
|
||||||
Polygon.cpp
|
Polygon.cpp
|
||||||
Polygon.hpp
|
Polygon.hpp
|
||||||
|
MutablePolygon.cpp
|
||||||
|
MutablePolygon.hpp
|
||||||
PolygonTrimmer.cpp
|
PolygonTrimmer.cpp
|
||||||
PolygonTrimmer.hpp
|
PolygonTrimmer.hpp
|
||||||
Polyline.cpp
|
Polyline.cpp
|
||||||
|
|
242
src/libslic3r/MutablePolygon.cpp
Normal file
242
src/libslic3r/MutablePolygon.cpp
Normal file
|
@ -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<double>().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<typename VectorType>
|
||||||
|
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<double>().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<double>();
|
||||||
|
const Vec2d p2 = it2->cast<double>();
|
||||||
|
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<double>();
|
||||||
|
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<double>();
|
||||||
|
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<double>();
|
||||||
|
const Vec2d p2 = it2->cast<double>();
|
||||||
|
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<double>();
|
||||||
|
const Vec2d p2_2 = it2.next()->cast<double>();
|
||||||
|
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<coord_t>());
|
||||||
|
it2 = it2.insert((p2 + (p2_2 - p2) * t).cast<coord_t>());
|
||||||
|
} else if (! backward_is_blocked) {
|
||||||
|
it0 = it0.prev().insert(point_on_line_at_dist(p0, Vec2d(it0.prev()->cast<double>()), p2, shortcut_length).cast<coord_t>());
|
||||||
|
} else if (! forward_is_blocked) {
|
||||||
|
it2 = it2.insert(point_on_line_at_dist(p2, Vec2d(it2.next()->cast<double>()), p0, shortcut_length).cast<coord_t>());
|
||||||
|
} 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<double>(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<double>();
|
||||||
|
auto it0 = it1.prev();
|
||||||
|
auto it2 = it1.next();
|
||||||
|
const Vec2d p0 = it0->cast<double>();
|
||||||
|
const Vec2d p2 = it2->cast<double>();
|
||||||
|
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<double>(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<coord_t>())
|
||||||
|
.insert((p1 + v2 * (shortcut_length / d2)).cast<coord_t>());
|
||||||
|
} else if (v1.squaredNorm() < v2.squaredNorm())
|
||||||
|
it0.insert(point_on_line_at_dist(p1, p2, p0, shortcut_length).cast<coord_t>());
|
||||||
|
else
|
||||||
|
it0.insert(point_on_line_at_dist(p1, p0, p2, shortcut_length).cast<coord_t>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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
|
227
src/libslic3r/MutablePolygon.hpp
Normal file
227
src/libslic3r/MutablePolygon.hpp
Normal file
|
@ -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<Point> rhs, size_t reserve = 0) : MutablePolygon(rhs.begin(), rhs.end(), reserve) {}
|
||||||
|
|
||||||
|
template<typename IT>
|
||||||
|
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<size_t>(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<LinkedPoint> 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_
|
|
@ -56,11 +56,21 @@ typedef Eigen::Transform<double, 3, Eigen::Affine, Eigen::DontAlign> Transform3d
|
||||||
|
|
||||||
inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0) || (lhs(0) == rhs(0) && lhs(1) < rhs(1)); }
|
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.
|
template<int Options>
|
||||||
//inline int32_t cross2(const Vec2i32 &v1, const Vec2i32 &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
|
int32_t cross2(const Eigen::MatrixBase<Eigen::Matrix<int32_t, 2, 1, Options>> &v1, const Eigen::MatrixBase<Eigen::Matrix<int32_t, 2, 1, Options>> &v2) = delete;
|
||||||
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); }
|
template<typename T, int Options>
|
||||||
inline double cross2(const Vec2d &v1, const Vec2d &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
|
inline T cross2(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> &v1, const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> &v2)
|
||||||
|
{
|
||||||
|
return v1(0) * v2(1) - v1(1) * v2(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Derived, typename Derived2>
|
||||||
|
inline typename Derived::Scalar cross2(const Eigen::MatrixBase<Derived> &v1, const Eigen::MatrixBase<Derived2> &v2)
|
||||||
|
{
|
||||||
|
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "cross2(): Scalar types of 1st and 2nd operand must be equal.");
|
||||||
|
return v1(0) * v2(1) - v1(1) * v2(0);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T, int Options>
|
template<typename T, int Options>
|
||||||
inline Eigen::Matrix<T, 2, 1, Eigen::DontAlign> perp(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> &v) { return Eigen::Matrix<T, 2, 1, Eigen::DontAlign>(- v.y(), v.x()); }
|
inline Eigen::Matrix<T, 2, 1, Eigen::DontAlign> perp(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>> &v) { return Eigen::Matrix<T, 2, 1, Eigen::DontAlign>(- v.y(), v.x()); }
|
||||||
|
|
|
@ -11,6 +11,7 @@ add_executable(${_TEST_NAME}_tests
|
||||||
test_geometry.cpp
|
test_geometry.cpp
|
||||||
test_placeholder_parser.cpp
|
test_placeholder_parser.cpp
|
||||||
test_polygon.cpp
|
test_polygon.cpp
|
||||||
|
test_mutable_polygon.cpp
|
||||||
test_stl.cpp
|
test_stl.cpp
|
||||||
test_meshsimplify.cpp
|
test_meshsimplify.cpp
|
||||||
test_meshboolean.cpp
|
test_meshboolean.cpp
|
||||||
|
|
145
tests/libslic3r/test_mutable_polygon.cpp
Normal file
145
tests/libslic3r/test_mutable_polygon.cpp
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
|
#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 } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue