377 lines
15 KiB
C++
377 lines
15 KiB
C++
#ifndef slic3r_MutablePolygon_hpp_
|
|
#define slic3r_MutablePolygon_hpp_
|
|
|
|
#include "Point.hpp"
|
|
#include "Polygon.hpp"
|
|
#include "ExPolygon.hpp"
|
|
|
|
namespace Slic3r {
|
|
|
|
// Polygon implemented as a loop of double linked elements.
|
|
// All elements are allocated in a single std::vector<>, thus integer indices are used for
|
|
// referencing the previous and next element and inside iterators to survive reallocation
|
|
// of the vector.
|
|
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()); return *m_data; }
|
|
IndexType size() const { assert(this->valid()); return 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()); return *m_data; }
|
|
IndexType size() const { assert(this->valid()); return m_data->size(); }
|
|
iterator& remove() { 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;
|
|
friend class range;
|
|
};
|
|
|
|
// Iterator range for maintaining a range of unprocessed items, see smooth_outward().
|
|
class range
|
|
{
|
|
public:
|
|
range(MutablePolygon& poly) : range(poly.begin(), poly.end()) {}
|
|
range(MutablePolygon::iterator begin, MutablePolygon::iterator end) : m_begin(begin), m_end(end) {}
|
|
|
|
// Start of a range, inclusive. If range is empty, then ! begin().valid().
|
|
MutablePolygon::iterator begin() const { return m_begin; }
|
|
// End of a range, inclusive. If range is empty, then ! end().valid().
|
|
MutablePolygon::iterator end() const { return m_end; }
|
|
// Is the range empty?
|
|
bool empty() const { return !m_begin.valid(); }
|
|
|
|
// Return begin() and shorten the range by advancing front.
|
|
MutablePolygon::iterator process_next() {
|
|
assert(!this->empty());
|
|
MutablePolygon::iterator out = m_begin;
|
|
this->advance_front();
|
|
return out;
|
|
}
|
|
|
|
void advance_front() {
|
|
assert(! this->empty());
|
|
if (m_begin == m_end)
|
|
this->make_empty();
|
|
else
|
|
++ m_begin;
|
|
}
|
|
|
|
void retract_back() {
|
|
assert(! this->empty());
|
|
if (m_begin == m_end)
|
|
this->make_empty();
|
|
else
|
|
-- m_end;
|
|
}
|
|
|
|
MutablePolygon::iterator remove_front(MutablePolygon::iterator it) {
|
|
if (! this->empty() && m_begin == it)
|
|
this->advance_front();
|
|
return it.remove();
|
|
}
|
|
|
|
MutablePolygon::iterator remove_back(MutablePolygon::iterator it) {
|
|
if (! this->empty() && m_end == it)
|
|
this->retract_back();
|
|
return it.remove();
|
|
}
|
|
|
|
private:
|
|
// Range from begin to end, inclusive.
|
|
// If the range is valid, then both m_begin and m_end are invalid.
|
|
MutablePolygon::iterator m_begin;
|
|
MutablePolygon::iterator m_end;
|
|
|
|
void make_empty() {
|
|
m_begin.m_idx = -1;
|
|
m_end.m_idx = -1;
|
|
}
|
|
};
|
|
|
|
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) {
|
|
this->assign_inner(begin, end, reserve);
|
|
};
|
|
|
|
template<typename IT>
|
|
void assign(IT begin, IT end, size_t reserve = 0) {
|
|
m_data.clear();
|
|
m_head = IndexType(-1);
|
|
m_head_free = { IndexType(-1) };
|
|
this->assign_inner(begin, end, reserve);
|
|
};
|
|
|
|
void assign(const Polygon &rhs, size_t reserve = 0) {
|
|
assign(rhs.points.begin(), rhs.points.end(), reserve);
|
|
}
|
|
|
|
void polygon(Polygon &out) const {
|
|
out.points.clear();
|
|
if (this->valid()) {
|
|
out.points.reserve(this->size());
|
|
auto it = this->cbegin();
|
|
out.points.emplace_back(*it);
|
|
for (++ it; it != this->cbegin(); ++ it)
|
|
out.points.emplace_back(*it);
|
|
}
|
|
};
|
|
|
|
Polygon polygon() const {
|
|
Polygon out;
|
|
this->polygon(out);
|
|
return out;
|
|
};
|
|
|
|
bool empty() const { return m_size == 0; }
|
|
size_t size() const { return m_size; }
|
|
size_t capacity() const { return m_data.capacity(); }
|
|
bool valid() const { return m_size >= 3; }
|
|
void clear() { m_data.clear(); m_size = 0; m_head = IndexType(-1); m_head_free = IndexType(-1); }
|
|
|
|
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 {
|
|
// 8 bytes
|
|
PointType point;
|
|
// 4 bytes
|
|
IndexType prev;
|
|
// 4 bytes
|
|
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]; }
|
|
|
|
template<typename IT>
|
|
void assign_inner(IT begin, IT end, size_t reserve) {
|
|
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;
|
|
}
|
|
};
|
|
|
|
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);
|
|
|
|
// Remove nearly duplicate points. If a distance between two points is less than scaled_eps
|
|
// and if the angle between its surrounding lines is less than max_angle, the point will be removed.
|
|
// May reduce the polygon down to empty polygon.
|
|
void remove_duplicates(MutablePolygon &polygon, coord_t scaled_eps, const double max_angle);
|
|
inline ExPolygons remove_duplicates(ExPolygons expolygons, coord_t scaled_eps, double max_angle)
|
|
{
|
|
MutablePolygon mp;
|
|
for (ExPolygon &expolygon : expolygons) {
|
|
mp.assign(expolygon.contour, expolygon.contour.size() * 2);
|
|
remove_duplicates(mp, scaled_eps, max_angle);
|
|
mp.polygon(expolygon.contour);
|
|
for (Polygon &hole : expolygon.holes) {
|
|
mp.assign(hole, hole.size() * 2);
|
|
remove_duplicates(mp, scaled_eps, max_angle);
|
|
mp.polygon(hole);
|
|
}
|
|
expolygon.holes.erase(std::remove_if(expolygon.holes.begin(), expolygon.holes.end(), [](const auto &p) { return p.empty(); }), expolygon.holes.end());
|
|
}
|
|
expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), [](const auto &p) { return p.empty(); }), expolygons.end());
|
|
return expolygons;
|
|
}
|
|
|
|
void smooth_outward(MutablePolygon &polygon, coord_t clip_dist_scaled);
|
|
|
|
inline Polygon smooth_outward(Polygon polygon, coord_t clip_dist_scaled)
|
|
{
|
|
MutablePolygon mp(polygon, polygon.size() * 2);
|
|
smooth_outward(mp, clip_dist_scaled);
|
|
mp.polygon(polygon);
|
|
return polygon;
|
|
}
|
|
|
|
inline Polygons smooth_outward(Polygons polygons, coord_t clip_dist_scaled)
|
|
{
|
|
MutablePolygon mp;
|
|
for (Polygon &polygon : polygons) {
|
|
mp.assign(polygon, polygon.size() * 2);
|
|
smooth_outward(mp, clip_dist_scaled);
|
|
mp.polygon(polygon);
|
|
}
|
|
polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const auto &p){ return p.empty(); }), polygons.end());
|
|
return polygons;
|
|
}
|
|
|
|
inline ExPolygons smooth_outward(ExPolygons expolygons, coord_t clip_dist_scaled)
|
|
{
|
|
MutablePolygon mp;
|
|
for (ExPolygon &expolygon : expolygons) {
|
|
mp.assign(expolygon.contour, expolygon.contour.size() * 2);
|
|
smooth_outward(mp, clip_dist_scaled);
|
|
mp.polygon(expolygon.contour);
|
|
for (Polygon &hole : expolygon.holes) {
|
|
mp.assign(hole, hole.size() * 2);
|
|
smooth_outward(mp, clip_dist_scaled);
|
|
mp.polygon(hole);
|
|
}
|
|
expolygon.holes.erase(std::remove_if(expolygon.holes.begin(), expolygon.holes.end(), [](const auto &p) { return p.empty(); }), expolygon.holes.end());
|
|
}
|
|
expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), [](const auto &p) { return p.empty(); }), expolygons.end());
|
|
return expolygons;
|
|
}
|
|
|
|
}
|
|
|
|
#endif // slic3r_MutablePolygon_hpp_
|