OrcaSlicer/src/libnest2d/tools/libnfporb/libnfporb.hpp
bubnikv 0558b53493 WIP: Moved sources int src/, separated most of the source code from Perl.
The XS was left only for the unit / integration tests, and it links
libslic3r only. No wxWidgets are allowed to be used from Perl starting
from now.
2018-09-19 11:02:24 +02:00

1547 lines
42 KiB
C++

#ifndef NFP_HPP_
#define NFP_HPP_
#include <iostream>
#include <list>
#include <string>
#include <fstream>
#include <streambuf>
#include <vector>
#include <set>
#include <exception>
#include <random>
#include <limits>
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
#define LIBNFP_NOEXCEPT
#define LIBNFP_CONSTEXPR
#elif __cplusplus >= 201103L
#define LIBNFP_NOEXCEPT noexcept
#define LIBNFP_CONSTEXPR constexpr
#endif
#ifdef LIBNFP_USE_RATIONAL
#include <boost/multiprecision/gmp.hpp>
#include <boost/multiprecision/number.hpp>
#endif
#include <boost/geometry.hpp>
#include <boost/geometry/util/math.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/io/svg/svg_mapper.hpp>
#include <boost/geometry/algorithms/intersects.hpp>
#include <boost/geometry/geometries/register/point.hpp>
#ifdef LIBNFP_USE_RATIONAL
namespace bm = boost::multiprecision;
#endif
namespace bg = boost::geometry;
namespace trans = boost::geometry::strategy::transform;
namespace libnfporb {
#ifdef NFP_DEBUG
#define DEBUG_VAL(x) std::cerr << x << std::endl;
#define DEBUG_MSG(title, value) std::cerr << title << ":" << value << std::endl;
#else
#define DEBUG_VAL(x)
#define DEBUG_MSG(title, value)
#endif
using std::string;
static LIBNFP_CONSTEXPR long double NFP_EPSILON=0.00000001;
class LongDouble {
private:
long double val_;
public:
LongDouble() : val_(0) {
}
LongDouble(const long double& val) : val_(val) {
}
void setVal(const long double& v) {
val_ = v;
}
long double val() const {
return val_;
}
LongDouble operator/(const LongDouble& other) const {
return this->val_ / other.val_;
}
LongDouble operator*(const LongDouble& other) const {
return this->val_ * other.val_;
}
LongDouble operator-(const LongDouble& other) const {
return this->val_ - other.val_;
}
LongDouble operator-() const {
return this->val_ * -1;
}
LongDouble operator+(const LongDouble& other) const {
return this->val_ + other.val_;
}
void operator/=(const LongDouble& other) {
this->val_ = this->val_ / other.val_;
}
void operator*=(const LongDouble& other) {
this->val_ = this->val_ * other.val_;
}
void operator-=(const LongDouble& other) {
this->val_ = this->val_ - other.val_;
}
void operator+=(const LongDouble& other) {
this->val_ = this->val_ + other.val_;
}
bool operator==(const int& other) const {
return this->operator ==(static_cast<long double>(other));
}
bool operator==(const LongDouble& other) const {
return this->operator ==(other.val());
}
bool operator==(const long double& other) const {
return this->val() == other;
}
bool operator!=(const int& other) const {
return !this->operator ==(other);
}
bool operator!=(const LongDouble& other) const {
return !this->operator ==(other);
}
bool operator!=(const long double& other) const {
return !this->operator ==(other);
}
bool operator<(const int& other) const {
return this->operator <(static_cast<long double>(other));
}
bool operator<(const LongDouble& other) const {
return this->operator <(other.val());
}
bool operator<(const long double& other) const {
return this->val() < other;
}
bool operator>(const int& other) const {
return this->operator >(static_cast<long double>(other));
}
bool operator>(const LongDouble& other) const {
return this->operator >(other.val());
}
bool operator>(const long double& other) const {
return this->val() > other;
}
bool operator>=(const int& other) const {
return this->operator >=(static_cast<long double>(other));
}
bool operator>=(const LongDouble& other) const {
return this->operator >=(other.val());
}
bool operator>=(const long double& other) const {
return this->val() >= other;
}
bool operator<=(const int& other) const {
return this->operator <=(static_cast<long double>(other));
}
bool operator<=(const LongDouble& other) const {
return this->operator <=(other.val());
}
bool operator<=(const long double& other) const {
return this->val() <= other;
}
};
}
namespace std {
template<>
struct numeric_limits<libnfporb::LongDouble>
{
static const LIBNFP_CONSTEXPR bool is_specialized = true;
static const LIBNFP_CONSTEXPR long double
min() LIBNFP_NOEXCEPT { return std::numeric_limits<long double>::min(); }
static LIBNFP_CONSTEXPR long double
max() LIBNFP_NOEXCEPT { return std::numeric_limits<long double>::max(); }
#if __cplusplus >= 201103L
static LIBNFP_CONSTEXPR long double
lowest() LIBNFP_NOEXCEPT { return -std::numeric_limits<long double>::lowest(); }
#endif
static const LIBNFP_CONSTEXPR int digits = std::numeric_limits<long double>::digits;
static const LIBNFP_CONSTEXPR int digits10 = std::numeric_limits<long double>::digits10;
#if __cplusplus >= 201103L
static const LIBNFP_CONSTEXPR int max_digits10
= std::numeric_limits<long double>::max_digits10;
#endif
static const LIBNFP_CONSTEXPR bool is_signed = true;
static const LIBNFP_CONSTEXPR bool is_integer = false;
static const LIBNFP_CONSTEXPR bool is_exact = false;
static const LIBNFP_CONSTEXPR int radix = std::numeric_limits<long double>::radix;
static const LIBNFP_CONSTEXPR long double
epsilon() LIBNFP_NOEXCEPT { return libnfporb::NFP_EPSILON; }
static const LIBNFP_CONSTEXPR long double
round_error() LIBNFP_NOEXCEPT { return 0.5L; }
static const LIBNFP_CONSTEXPR int min_exponent = std::numeric_limits<long double>::min_exponent;
static const LIBNFP_CONSTEXPR int min_exponent10 = std::numeric_limits<long double>::min_exponent10;
static const LIBNFP_CONSTEXPR int max_exponent = std::numeric_limits<long double>::max_exponent;
static const LIBNFP_CONSTEXPR int max_exponent10 = std::numeric_limits<long double>::max_exponent10;
static const LIBNFP_CONSTEXPR bool has_infinity = std::numeric_limits<long double>::has_infinity;
static const LIBNFP_CONSTEXPR bool has_quiet_NaN = std::numeric_limits<long double>::has_quiet_NaN;
static const LIBNFP_CONSTEXPR bool has_signaling_NaN = has_quiet_NaN;
static const LIBNFP_CONSTEXPR float_denorm_style has_denorm
= std::numeric_limits<long double>::has_denorm;
static const LIBNFP_CONSTEXPR bool has_denorm_loss
= std::numeric_limits<long double>::has_denorm_loss;
static const LIBNFP_CONSTEXPR long double
infinity() LIBNFP_NOEXCEPT { return std::numeric_limits<long double>::infinity(); }
static const LIBNFP_CONSTEXPR long double
quiet_NaN() LIBNFP_NOEXCEPT { return std::numeric_limits<long double>::quiet_NaN(); }
static const LIBNFP_CONSTEXPR long double
signaling_NaN() LIBNFP_NOEXCEPT { return std::numeric_limits<long double>::signaling_NaN(); }
static const LIBNFP_CONSTEXPR long double
denorm_min() LIBNFP_NOEXCEPT { return std::numeric_limits<long double>::denorm_min(); }
static const LIBNFP_CONSTEXPR bool is_iec559
= has_infinity && has_quiet_NaN && has_denorm == denorm_present;
static const LIBNFP_CONSTEXPR bool is_bounded = true;
static const LIBNFP_CONSTEXPR bool is_modulo = false;
static const LIBNFP_CONSTEXPR bool traps = std::numeric_limits<long double>::traps;
static const LIBNFP_CONSTEXPR bool tinyness_before =
std::numeric_limits<long double>::tinyness_before;
static const LIBNFP_CONSTEXPR float_round_style round_style =
round_to_nearest;
};
}
namespace boost {
namespace numeric {
template<>
struct raw_converter<boost::numeric::conversion_traits<double, libnfporb::LongDouble>>
{
typedef boost::numeric::conversion_traits<double, libnfporb::LongDouble>::result_type result_type ;
typedef boost::numeric::conversion_traits<double, libnfporb::LongDouble>::argument_type argument_type ;
static result_type low_level_convert ( argument_type s ) { return s.val() ; }
} ;
}
}
namespace libnfporb {
#ifndef LIBNFP_USE_RATIONAL
typedef LongDouble coord_t;
#else
typedef bm::number<bm::gmp_rational, bm::et_off> rational_t;
typedef rational_t coord_t;
#endif
bool equals(const LongDouble& lhs, const LongDouble& rhs);
#ifdef LIBNFP_USE_RATIONAL
bool equals(const rational_t& lhs, const rational_t& rhs);
#endif
bool equals(const long double& lhs, const long double& rhs);
const coord_t MAX_COORD = 999999999999999999.0;
const coord_t MIN_COORD = std::numeric_limits<coord_t>::min();
class point_t {
public:
point_t() : x_(0), y_(0) {
}
point_t(coord_t x, coord_t y) : x_(x), y_(y) {
}
bool marked_ = false;
coord_t x_;
coord_t y_;
point_t operator-(const point_t& other) const {
point_t result = *this;
bg::subtract_point(result, other);
return result;
}
point_t operator+(const point_t& other) const {
point_t result = *this;
bg::add_point(result, other);
return result;
}
bool operator==(const point_t& other) const {
return bg::equals(this, other);
}
bool operator!=(const point_t& other) const {
return !this->operator ==(other);
}
bool operator<(const point_t& other) const {
return boost::geometry::math::smaller(this->x_, other.x_) || (equals(this->x_, other.x_) && boost::geometry::math::smaller(this->y_, other.y_));
}
};
inline long double toLongDouble(const LongDouble& c) {
return c.val();
}
#ifdef LIBNFP_USE_RATIONAL
inline long double toLongDouble(const rational_t& c) {
return bm::numerator(c).convert_to<long double>() / bm::denominator(c).convert_to<long double>();
}
#endif
std::ostream& operator<<(std::ostream& os, const coord_t& p) {
os << toLongDouble(p);
return os;
}
std::istream& operator>>(std::istream& is, LongDouble& c) {
long double val;
is >> val;
c.setVal(val);
return is;
}
std::ostream& operator<<(std::ostream& os, const point_t& p) {
os << "{" << toLongDouble(p.x_) << "," << toLongDouble(p.y_) << "}";
return os;
}
const point_t INVALID_POINT = {MAX_COORD, MAX_COORD};
typedef bg::model::segment<point_t> segment_t;
}
#ifdef LIBNFP_USE_RATIONAL
inline long double acos(const libnfporb::rational_t& r) {
return acos(libnfporb::toLongDouble(r));
}
#endif
inline long double acos(const libnfporb::LongDouble& ld) {
return acos(libnfporb::toLongDouble(ld));
}
#ifdef LIBNFP_USE_RATIONAL
inline long double sqrt(const libnfporb::rational_t& r) {
return sqrt(libnfporb::toLongDouble(r));
}
#endif
inline long double sqrt(const libnfporb::LongDouble& ld) {
return sqrt(libnfporb::toLongDouble(ld));
}
BOOST_GEOMETRY_REGISTER_POINT_2D(libnfporb::point_t, libnfporb::coord_t, cs::cartesian, x_, y_)
namespace boost {
namespace geometry {
namespace math {
namespace detail {
template <>
struct square_root<libnfporb::LongDouble>
{
typedef libnfporb::LongDouble return_type;
static inline libnfporb::LongDouble apply(libnfporb::LongDouble const& a)
{
return std::sqrt(a.val());
}
};
#ifdef LIBNFP_USE_RATIONAL
template <>
struct square_root<libnfporb::rational_t>
{
typedef libnfporb::rational_t return_type;
static inline libnfporb::rational_t apply(libnfporb::rational_t const& a)
{
return std::sqrt(libnfporb::toLongDouble(a));
}
};
#endif
template<>
struct abs<libnfporb::LongDouble>
{
static libnfporb::LongDouble apply(libnfporb::LongDouble const& value)
{
libnfporb::LongDouble const zero = libnfporb::LongDouble();
return value.val() < zero.val() ? -value.val() : value.val();
}
};
template <>
struct equals<libnfporb::LongDouble, false>
{
template<typename Policy>
static inline bool apply(libnfporb::LongDouble const& lhs, libnfporb::LongDouble const& rhs, Policy const& policy)
{
if(lhs.val() == rhs.val())
return true;
return bg::math::detail::abs<libnfporb::LongDouble>::apply(lhs.val() - rhs.val()) <= policy.apply(lhs.val(), rhs.val()) * libnfporb::NFP_EPSILON;
}
};
template <>
struct smaller<libnfporb::LongDouble>
{
static inline bool apply(libnfporb::LongDouble const& lhs, libnfporb::LongDouble const& rhs)
{
if(lhs.val() == rhs.val() || bg::math::detail::abs<libnfporb::LongDouble>::apply(lhs.val() - rhs.val()) <= libnfporb::NFP_EPSILON * std::max(lhs.val(), rhs.val()))
return false;
return lhs < rhs;
}
};
}
}
}
}
namespace libnfporb {
inline bool smaller(const LongDouble& lhs, const LongDouble& rhs) {
return boost::geometry::math::detail::smaller<LongDouble>::apply(lhs, rhs);
}
inline bool larger(const LongDouble& lhs, const LongDouble& rhs) {
return smaller(rhs, lhs);
}
bool equals(const LongDouble& lhs, const LongDouble& rhs) {
if(lhs.val() == rhs.val())
return true;
return bg::math::detail::abs<libnfporb::LongDouble>::apply(lhs.val() - rhs.val()) <= libnfporb::NFP_EPSILON * std::max(lhs.val(), rhs.val());
}
#ifdef LIBNFP_USE_RATIONAL
inline bool smaller(const rational_t& lhs, const rational_t& rhs) {
return lhs < rhs;
}
inline bool larger(const rational_t& lhs, const rational_t& rhs) {
return smaller(rhs, lhs);
}
bool equals(const rational_t& lhs, const rational_t& rhs) {
return lhs == rhs;
}
#endif
inline bool smaller(const long double& lhs, const long double& rhs) {
return lhs < rhs;
}
inline bool larger(const long double& lhs, const long double& rhs) {
return smaller(rhs, lhs);
}
bool equals(const long double& lhs, const long double& rhs) {
return lhs == rhs;
}
typedef bg::model::polygon<point_t, false, true> polygon_t;
typedef std::vector<polygon_t::ring_type> nfp_t;
typedef bg::model::linestring<point_t> linestring_t;
typedef polygon_t::ring_type::size_type psize_t;
typedef bg::model::d2::point_xy<long double> pointf_t;
typedef bg::model::segment<pointf_t> segmentf_t;
typedef bg::model::polygon<pointf_t, false, true> polygonf_t;
polygonf_t::ring_type convert(const polygon_t::ring_type& r) {
polygonf_t::ring_type rf;
for(const auto& pt : r) {
rf.push_back(pointf_t(toLongDouble(pt.x_), toLongDouble(pt.y_)));
}
return rf;
}
polygonf_t convert(polygon_t p) {
polygonf_t pf;
pf.outer() = convert(p.outer());
for(const auto& r : p.inners()) {
pf.inners().push_back(convert(r));
}
return pf;
}
polygon_t nfpRingsToNfpPoly(const nfp_t& nfp) {
polygon_t nfppoly;
for (const auto& pt : nfp.front()) {
nfppoly.outer().push_back(pt);
}
for (size_t i = 1; i < nfp.size(); ++i) {
nfppoly.inners().push_back({});
for (const auto& pt : nfp[i]) {
nfppoly.inners().back().push_back(pt);
}
}
return nfppoly;
}
void write_svg(std::string const& filename,const std::vector<segment_t>& segments) {
std::ofstream svg(filename.c_str());
boost::geometry::svg_mapper<pointf_t> mapper(svg, 100, 100, "width=\"200mm\" height=\"200mm\" viewBox=\"-250 -250 500 500\"");
for(const auto& seg : segments) {
segmentf_t segf({toLongDouble(seg.first.x_), toLongDouble(seg.first.y_)}, {toLongDouble(seg.second.x_), toLongDouble(seg.second.y_)});
mapper.add(segf);
mapper.map(segf, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2");
}
}
void write_svg(std::string const& filename, const polygon_t& p, const polygon_t::ring_type& ring) {
std::ofstream svg(filename.c_str());
boost::geometry::svg_mapper<pointf_t> mapper(svg, 100, 100, "width=\"200mm\" height=\"200mm\" viewBox=\"-250 -250 500 500\"");
auto pf = convert(p);
auto rf = convert(ring);
mapper.add(pf);
mapper.map(pf, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2");
mapper.add(rf);
mapper.map(rf, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2");
}
void write_svg(std::string const& filename, std::vector<polygon_t> const& polygons) {
std::ofstream svg(filename.c_str());
boost::geometry::svg_mapper<pointf_t> mapper(svg, 100, 100, "width=\"200mm\" height=\"200mm\" viewBox=\"-250 -250 500 500\"");
for (auto p : polygons) {
auto pf = convert(p);
mapper.add(pf);
mapper.map(pf, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2");
}
}
void write_svg(std::string const& filename, std::vector<polygon_t> const& polygons, const nfp_t& nfp) {
polygon_t nfppoly;
for (const auto& pt : nfp.front()) {
nfppoly.outer().push_back(pt);
}
for (size_t i = 1; i < nfp.size(); ++i) {
nfppoly.inners().push_back({});
for (const auto& pt : nfp[i]) {
nfppoly.inners().back().push_back(pt);
}
}
std::ofstream svg(filename.c_str());
boost::geometry::svg_mapper<pointf_t> mapper(svg, 100, 100, "width=\"200mm\" height=\"200mm\" viewBox=\"-250 -250 500 500\"");
for (auto p : polygons) {
auto pf = convert(p);
mapper.add(pf);
mapper.map(pf, "fill-opacity:0.5;fill:rgb(153,204,0);stroke:rgb(153,204,0);stroke-width:2");
}
bg::correct(nfppoly);
auto nfpf = convert(nfppoly);
mapper.add(nfpf);
mapper.map(nfpf, "fill-opacity:0.5;fill:rgb(204,153,0);stroke:rgb(204,153,0);stroke-width:2");
for(auto& r: nfpf.inners()) {
if(r.size() == 1) {
mapper.add(r.front());
mapper.map(r.front(), "fill-opacity:0.5;fill:rgb(204,153,0);stroke:rgb(204,153,0);stroke-width:2");
} else if(r.size() == 2) {
segmentf_t seg(r.front(), *(r.begin()+1));
mapper.add(seg);
mapper.map(seg, "fill-opacity:0.5;fill:rgb(204,153,0);stroke:rgb(204,153,0);stroke-width:2");
}
}
}
std::ostream& operator<<(std::ostream& os, const segment_t& seg) {
os << "{" << seg.first << "," << seg.second << "}";
return os;
}
bool operator<(const segment_t& lhs, const segment_t& rhs) {
return lhs.first < rhs.first || ((lhs.first == rhs.first) && (lhs.second < rhs.second));
}
bool operator==(const segment_t& lhs, const segment_t& rhs) {
return (lhs.first == rhs.first && lhs.second == rhs.second) || (lhs.first == rhs.second && lhs.second == rhs.first);
}
bool operator!=(const segment_t& lhs, const segment_t& rhs) {
return !operator==(lhs,rhs);
}
enum Alignment {
LEFT,
RIGHT,
ON
};
point_t normalize(const point_t& pt) {
point_t norm = pt;
coord_t len = bg::length(segment_t{{0,0},pt});
if(len == 0.0L)
return {0,0};
norm.x_ /= len;
norm.y_ /= len;
return norm;
}
Alignment get_alignment(const segment_t& seg, const point_t& pt){
coord_t res = ((seg.second.x_ - seg.first.x_)*(pt.y_ - seg.first.y_)
- (seg.second.y_ - seg.first.y_)*(pt.x_ - seg.first.x_));
if(equals(res, 0)) {
return ON;
} else if(larger(res,0)) {
return LEFT;
} else {
return RIGHT;
}
}
long double get_inner_angle(const point_t& joint, const point_t& end1, const point_t& end2) {
coord_t dx21 = end1.x_-joint.x_;
coord_t dx31 = end2.x_-joint.x_;
coord_t dy21 = end1.y_-joint.y_;
coord_t dy31 = end2.y_-joint.y_;
coord_t m12 = sqrt((dx21*dx21 + dy21*dy21));
coord_t m13 = sqrt((dx31*dx31 + dy31*dy31));
if(m12 == 0.0L || m13 == 0.0L)
return 0;
return acos( (dx21*dx31 + dy21*dy31) / (m12 * m13) );
}
struct TouchingPoint {
enum Type {
VERTEX,
A_ON_B,
B_ON_A
};
Type type_;
psize_t A_;
psize_t B_;
};
struct TranslationVector {
point_t vector_;
segment_t edge_;
bool fromA_;
string name_;
bool operator<(const TranslationVector& other) const {
return this->vector_ < other.vector_ || ((this->vector_ == other.vector_) && (this->edge_ < other.edge_));
}
};
std::ostream& operator<<(std::ostream& os, const TranslationVector& tv) {
os << "{" << tv.edge_ << " -> " << tv.vector_ << "} = " << tv.name_;
return os;
}
void read_wkt_polygon(const string& filename, polygon_t& p) {
std::ifstream t(filename);
std::string str;
t.seekg(0, std::ios::end);
str.reserve(t.tellg());
t.seekg(0, std::ios::beg);
str.assign((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
str.pop_back();
bg::read_wkt(str, p);
bg::correct(p);
}
std::vector<psize_t> find_minimum_y(const polygon_t& p) {
std::vector<psize_t> result;
coord_t min = MAX_COORD;
auto& po = p.outer();
for(psize_t i = 0; i < p.outer().size() - 1; ++i) {
if(smaller(po[i].y_, min)) {
result.clear();
min = po[i].y_;
result.push_back(i);
} else if (equals(po[i].y_, min)) {
result.push_back(i);
}
}
return result;
}
std::vector<psize_t> find_maximum_y(const polygon_t& p) {
std::vector<psize_t> result;
coord_t max = MIN_COORD;
auto& po = p.outer();
for(psize_t i = 0; i < p.outer().size() - 1; ++i) {
if(larger(po[i].y_, max)) {
result.clear();
max = po[i].y_;
result.push_back(i);
} else if (equals(po[i].y_, max)) {
result.push_back(i);
}
}
return result;
}
psize_t find_point(const polygon_t::ring_type& ring, const point_t& pt) {
for(psize_t i = 0; i < ring.size(); ++i) {
if(ring[i] == pt)
return i;
}
return std::numeric_limits<psize_t>::max();
}
std::vector<TouchingPoint> findTouchingPoints(const polygon_t::ring_type& ringA, const polygon_t::ring_type& ringB) {
std::vector<TouchingPoint> touchers;
for(psize_t i = 0; i < ringA.size() - 1; i++) {
psize_t nextI = i+1;
for(psize_t j = 0; j < ringB.size() - 1; j++) {
psize_t nextJ = j+1;
if(ringA[i] == ringB[j]) {
touchers.push_back({TouchingPoint::VERTEX, i, j});
} else if (ringA[nextI] != ringB[j] && bg::intersects(segment_t(ringA[i],ringA[nextI]), ringB[j])) {
touchers.push_back({TouchingPoint::B_ON_A, nextI, j});
} else if (ringB[nextJ] != ringA[i] && bg::intersects(segment_t(ringB[j],ringB[nextJ]), ringA[i])) {
touchers.push_back({TouchingPoint::A_ON_B, i, nextJ});
}
}
}
return touchers;
}
//TODO deduplicate code
TranslationVector trimVector(const polygon_t::ring_type& rA, const polygon_t::ring_type& rB, const TranslationVector& tv) {
coord_t shortest = bg::length(tv.edge_);
TranslationVector trimmed = tv;
for(const auto& ptA : rA) {
point_t translated;
//for polygon A we invert the translation
trans::translate_transformer<coord_t, 2, 2> translate(-tv.vector_.x_, -tv.vector_.y_);
boost::geometry::transform(ptA, translated, translate);
linestring_t projection;
segment_t segproj(ptA, translated);
projection.push_back(ptA);
projection.push_back(translated);
std::vector<point_t> intersections;
bg::intersection(rB, projection, intersections);
if(bg::touches(projection, rB) && intersections.size() < 2) {
continue;
}
//find shortest intersection
coord_t len;
segment_t segi;
for(const auto& pti : intersections) {
segi = segment_t(ptA,pti);
len = bg::length(segi);
if(smaller(len, shortest)) {
trimmed.vector_ = ptA - pti;
trimmed.edge_ = segi;
shortest = len;
}
}
}
for(const auto& ptB : rB) {
point_t translated;
trans::translate_transformer<coord_t, 2, 2> translate(tv.vector_.x_, tv.vector_.y_);
boost::geometry::transform(ptB, translated, translate);
linestring_t projection;
segment_t segproj(ptB, translated);
projection.push_back(ptB);
projection.push_back(translated);
std::vector<point_t> intersections;
bg::intersection(rA, projection, intersections);
if(bg::touches(projection, rA) && intersections.size() < 2) {
continue;
}
//find shortest intersection
coord_t len;
segment_t segi;
for(const auto& pti : intersections) {
segi = segment_t(ptB,pti);
len = bg::length(segi);
if(smaller(len, shortest)) {
trimmed.vector_ = pti - ptB;
trimmed.edge_ = segi;
shortest = len;
}
}
}
return trimmed;
}
std::vector<TranslationVector> findFeasibleTranslationVectors(polygon_t::ring_type& ringA, polygon_t::ring_type& ringB, const std::vector<TouchingPoint>& touchers) {
//use a set to automatically filter duplicate vectors
std::vector<TranslationVector> potentialVectors;
std::vector<std::pair<segment_t,segment_t>> touchEdges;
for (psize_t i = 0; i < touchers.size(); i++) {
point_t& vertexA = ringA[touchers[i].A_];
vertexA.marked_ = true;
// adjacent A vertices
auto prevAindex = static_cast<signed long>(touchers[i].A_ - 1);
auto nextAindex = static_cast<signed long>(touchers[i].A_ + 1);
prevAindex = (prevAindex < 0) ? static_cast<signed long>(ringA.size() - 2) : prevAindex; // loop
nextAindex = (static_cast<psize_t>(nextAindex) >= ringA.size()) ? 1 : nextAindex; // loop
point_t& prevA = ringA[prevAindex];
point_t& nextA = ringA[nextAindex];
// adjacent B vertices
point_t& vertexB = ringB[touchers[i].B_];
auto prevBindex = static_cast<signed long>(touchers[i].B_ - 1);
auto nextBindex = static_cast<signed long>(touchers[i].B_ + 1);
prevBindex = (prevBindex < 0) ? static_cast<signed long>(ringB.size() - 2) : prevBindex; // loop
nextBindex = (static_cast<psize_t>(nextBindex) >= ringB.size()) ? 1 : nextBindex; // loop
point_t& prevB = ringB[prevBindex];
point_t& nextB = ringB[nextBindex];
if (touchers[i].type_ == TouchingPoint::VERTEX) {
segment_t a1 = { vertexA, nextA };
segment_t a2 = { vertexA, prevA };
segment_t b1 = { vertexB, nextB };
segment_t b2 = { vertexB, prevB };
//swap the segment elements so that always the first point is the touching point
//also make the second segment always a segment of ringB
touchEdges.push_back({a1, b1});
touchEdges.push_back({a1, b2});
touchEdges.push_back({a2, b1});
touchEdges.push_back({a2, b2});
#ifdef NFP_DEBUG
write_svg("touchersV" + std::to_string(i) + ".svg", {a1,a2,b1,b2});
#endif
//TODO test parallel edges for floating point stability
Alignment al;
//a1 and b1 meet at start vertex
al = get_alignment(a1, b1.second);
if(al == LEFT) {
potentialVectors.push_back({b1.first - b1.second, b1, false, "vertex1"});
} else if(al == RIGHT) {
potentialVectors.push_back({a1.second - a1.first, a1, true, "vertex2"});
} else {
potentialVectors.push_back({a1.second - a1.first, a1, true, "vertex3"});
}
//a1 and b2 meet at start and end
al = get_alignment(a1, b2.second);
if(al == LEFT) {
//no feasible translation
} else if(al == RIGHT) {
potentialVectors.push_back({a1.second - a1.first, a1, true, "vertex4"});
} else {
potentialVectors.push_back({a1.second - a1.first, a1, true, "vertex5"});
}
//a2 and b1 meet at end and start
al = get_alignment(a2, b1.second);
if(al == LEFT) {
//no feasible translation
} else if(al == RIGHT) {
potentialVectors.push_back({b1.first - b1.second, b1, false, "vertex6"});
} else {
potentialVectors.push_back({b1.first - b1.second, b1, false, "vertex7"});
}
} else if (touchers[i].type_ == TouchingPoint::B_ON_A) {
segment_t a1 = {vertexB, vertexA};
segment_t a2 = {vertexB, prevA};
segment_t b1 = {vertexB, prevB};
segment_t b2 = {vertexB, nextB};
touchEdges.push_back({a1, b1});
touchEdges.push_back({a1, b2});
touchEdges.push_back({a2, b1});
touchEdges.push_back({a2, b2});
#ifdef NFP_DEBUG
write_svg("touchersB" + std::to_string(i) + ".svg", {a1,a2,b1,b2});
#endif
potentialVectors.push_back({vertexA - vertexB, {vertexB, vertexA}, true, "bona"});
} else if (touchers[i].type_ == TouchingPoint::A_ON_B) {
//TODO testme
segment_t a1 = {vertexA, prevA};
segment_t a2 = {vertexA, nextA};
segment_t b1 = {vertexA, vertexB};
segment_t b2 = {vertexA, prevB};
#ifdef NFP_DEBUG
write_svg("touchersA" + std::to_string(i) + ".svg", {a1,a2,b1,b2});
#endif
touchEdges.push_back({a1, b1});
touchEdges.push_back({a2, b1});
touchEdges.push_back({a1, b2});
touchEdges.push_back({a2, b2});
potentialVectors.push_back({vertexA - vertexB, {vertexA, vertexB}, false, "aonb"});
}
}
//discard immediately intersecting translations
std::vector<TranslationVector> vectors;
for(const auto& v : potentialVectors) {
bool discarded = false;
for(const auto& sp : touchEdges) {
point_t normEdge = normalize(v.edge_.second - v.edge_.first);
point_t normFirst = normalize(sp.first.second - sp.first.first);
point_t normSecond = normalize(sp.second.second - sp.second.first);
Alignment a1 = get_alignment({{0,0},normEdge}, normFirst);
Alignment a2 = get_alignment({{0,0},normEdge}, normSecond);
if(a1 == a2 && a1 != ON) {
long double df = get_inner_angle({0,0},normEdge, normFirst);
long double ds = get_inner_angle({0,0},normEdge, normSecond);
point_t normIn = normalize(v.edge_.second - v.edge_.first);
if (equals(df, ds)) {
TranslationVector trimmed = trimVector(ringA,ringB, v);
polygon_t::ring_type translated;
trans::translate_transformer<coord_t, 2, 2> translate(trimmed.vector_.x_, trimmed.vector_.y_);
boost::geometry::transform(ringB, translated, translate);
if (!(bg::intersects(translated, ringA) && !bg::overlaps(translated, ringA) && !bg::covered_by(translated, ringA) && !bg::covered_by(ringA, translated))) {
discarded = true;
break;
}
} else {
if (normIn == normalize(v.vector_)) {
if (larger(ds, df)) {
discarded = true;
break;
}
} else {
if (smaller(ds, df)) {
discarded = true;
break;
}
}
}
}
}
if(!discarded)
vectors.push_back(v);
}
return vectors;
}
bool find(const std::vector<TranslationVector>& h, const TranslationVector& tv) {
for(const auto& htv : h) {
if(htv.vector_ == tv.vector_)
return true;
}
return false;
}
TranslationVector getLongest(const std::vector<TranslationVector>& tvs) {
coord_t len;
coord_t maxLen = MIN_COORD;
TranslationVector longest;
longest.vector_ = INVALID_POINT;
for(auto& tv : tvs) {
len = bg::length(segment_t{{0,0},tv.vector_});
if(larger(len, maxLen)) {
maxLen = len;
longest = tv;
}
}
return longest;
}
TranslationVector selectNextTranslationVector(const polygon_t& pA, const polygon_t::ring_type& rA, const polygon_t::ring_type& rB, const std::vector<TranslationVector>& tvs, const std::vector<TranslationVector>& history) {
if(!history.empty()) {
TranslationVector last = history.back();
std::vector<TranslationVector> historyCopy = history;
if(historyCopy.size() >= 2) {
historyCopy.erase(historyCopy.end() - 1);
historyCopy.erase(historyCopy.end() - 1);
if(historyCopy.size() > 4) {
historyCopy.erase(historyCopy.begin(), historyCopy.end() - 4);
}
} else {
historyCopy.clear();
}
DEBUG_MSG("last", last);
psize_t laterI = std::numeric_limits<psize_t>::max();
point_t previous = rA[0];
point_t next;
if(last.fromA_) {
for (psize_t i = 1; i < rA.size() + 1; ++i) {
if (i >= rA.size())
next = rA[i % rA.size()];
else
next = rA[i];
segment_t candidate( previous, next );
if(candidate == last.edge_) {
laterI = i;
break;
}
previous = next;
}
if (laterI == std::numeric_limits<psize_t>::max()) {
point_t later;
if (last.vector_ == (last.edge_.second - last.edge_.first)) {
later = last.edge_.second;
} else {
later = last.edge_.first;
}
laterI = find_point(rA, later);
}
} else {
point_t later;
if (last.vector_ == (last.edge_.second - last.edge_.first)) {
later = last.edge_.second;
} else {
later = last.edge_.first;
}
laterI = find_point(rA, later);
}
if (laterI == std::numeric_limits<psize_t>::max()) {
throw std::runtime_error(
"Internal error: Can't find later point of last edge");
}
std::vector<segment_t> viableEdges;
previous = rA[laterI];
for(psize_t i = laterI + 1; i < rA.size() + laterI + 1; ++i) {
if(i >= rA.size())
next = rA[i % rA.size()];
else
next = rA[i];
viableEdges.push_back({previous, next});
previous = next;
}
// auto rng = std::default_random_engine {};
// std::shuffle(std::begin(viableEdges), std::end(viableEdges), rng);
//search with consulting the history to prevent oscillation
std::vector<TranslationVector> viableTrans;
for(const auto& ve: viableEdges) {
for(const auto& tv : tvs) {
if((tv.fromA_ && (normalize(tv.vector_) == normalize(ve.second - ve.first))) && (tv.edge_ != last.edge_ || tv.vector_.x_ != -last.vector_.x_ || tv.vector_.y_ != -last.vector_.y_) && !find(historyCopy, tv)) {
viableTrans.push_back(tv);
}
}
for (const auto& tv : tvs) {
if (!tv.fromA_) {
point_t later;
if (tv.vector_ == (tv.edge_.second - tv.edge_.first) && (tv.edge_ != last.edge_ || tv.vector_.x_ != -last.vector_.x_ || tv.vector_.y_ != -last.vector_.y_) && !find(historyCopy, tv)) {
later = tv.edge_.second;
} else if (tv.vector_ == (tv.edge_.first - tv.edge_.second)) {
later = tv.edge_.first;
} else
continue;
if (later == ve.first || later == ve.second) {
viableTrans.push_back(tv);
}
}
}
}
if(!viableTrans.empty())
return getLongest(viableTrans);
//search again without the history
for(const auto& ve: viableEdges) {
for(const auto& tv : tvs) {
if((tv.fromA_ && (normalize(tv.vector_) == normalize(ve.second - ve.first))) && (tv.edge_ != last.edge_ || tv.vector_.x_ != -last.vector_.x_ || tv.vector_.y_ != -last.vector_.y_)) {
viableTrans.push_back(tv);
}
}
for (const auto& tv : tvs) {
if (!tv.fromA_) {
point_t later;
if (tv.vector_ == (tv.edge_.second - tv.edge_.first) && (tv.edge_ != last.edge_ || tv.vector_.x_ != -last.vector_.x_ || tv.vector_.y_ != -last.vector_.y_)) {
later = tv.edge_.second;
} else if (tv.vector_ == (tv.edge_.first - tv.edge_.second)) {
later = tv.edge_.first;
} else
continue;
if (later == ve.first || later == ve.second) {
viableTrans.push_back(tv);
}
}
}
}
if(!viableTrans.empty())
return getLongest(viableTrans);
/*
//search again without the history and without checking last edge
for(const auto& ve: viableEdges) {
for(const auto& tv : tvs) {
if((tv.fromA_ && (normalize(tv.vector_) == normalize(ve.second - ve.first)))) {
return tv;
}
}
for (const auto& tv : tvs) {
if (!tv.fromA_) {
point_t later;
if (tv.vector_ == (tv.edge_.second - tv.edge_.first)) {
later = tv.edge_.second;
} else if (tv.vector_ == (tv.edge_.first - tv.edge_.second)) {
later = tv.edge_.first;
} else
continue;
if (later == ve.first || later == ve.second) {
return tv;
}
}
}
}*/
if(tvs.size() == 1)
return *tvs.begin();
TranslationVector tv;
tv.vector_ = INVALID_POINT;
return tv;
} else {
return getLongest(tvs);
}
}
bool inNfp(const point_t& pt, const nfp_t& nfp) {
for(const auto& r : nfp) {
if(bg::touches(pt, r))
return true;
}
return false;
}
enum SearchStartResult {
FIT,
FOUND,
NOT_FOUND
};
SearchStartResult searchStartTranslation(polygon_t::ring_type& rA, const polygon_t::ring_type& rB, const nfp_t& nfp,const bool& inside, point_t& result) {
for(psize_t i = 0; i < rA.size() - 1; i++) {
psize_t index;
if (i >= rA.size())
index = i % rA.size() + 1;
else
index = i;
auto& ptA = rA[index];
if(ptA.marked_)
continue;
ptA.marked_ = true;
for(const auto& ptB: rB) {
point_t testTranslation = ptA - ptB;
polygon_t::ring_type translated;
boost::geometry::transform(rB, translated, trans::translate_transformer<coord_t, 2, 2>(testTranslation.x_, testTranslation.y_));
//check if the translated rB is identical to rA
bool identical = false;
for(const auto& ptT: translated) {
identical = false;
for(const auto& ptA: rA) {
if(ptT == ptA) {
identical = true;
break;
}
}
if(!identical)
break;
}
if(identical) {
result = testTranslation;
return FIT;
}
bool bInside = false;
for(const auto& ptT: translated) {
if(bg::within(ptT, rA)) {
bInside = true;
break;
} else if(!bg::touches(ptT, rA)) {
bInside = false;
break;
}
}
if(((bInside && inside) || (!bInside && !inside)) && (!bg::overlaps(translated, rA) && !bg::covered_by(translated, rA) && !bg::covered_by(rA, translated)) && !inNfp(translated.front(), nfp)){
result = testTranslation;
return FOUND;
}
point_t nextPtA = rA[index + 1];
TranslationVector slideVector;
slideVector.vector_ = nextPtA - ptA;
slideVector.edge_ = {ptA, nextPtA};
slideVector.fromA_ = true;
TranslationVector trimmed = trimVector(rA, translated, slideVector);
polygon_t::ring_type translated2;
trans::translate_transformer<coord_t, 2, 2> trans(trimmed.vector_.x_, trimmed.vector_.y_);
boost::geometry::transform(translated, translated2, trans);
//check if the translated rB is identical to rA
identical = false;
for(const auto& ptT: translated) {
identical = false;
for(const auto& ptA: rA) {
if(ptT == ptA) {
identical = true;
break;
}
}
if(!identical)
break;
}
if(identical) {
result = trimmed.vector_ + testTranslation;
return FIT;
}
bInside = false;
for(const auto& ptT: translated2) {
if(bg::within(ptT, rA)) {
bInside = true;
break;
} else if(!bg::touches(ptT, rA)) {
bInside = false;
break;
}
}
if(((bInside && inside) || (!bInside && !inside)) && (!bg::overlaps(translated2, rA) && !bg::covered_by(translated2, rA) && !bg::covered_by(rA, translated2)) && !inNfp(translated2.front(), nfp)){
result = trimmed.vector_ + testTranslation;
return FOUND;
}
}
}
return NOT_FOUND;
}
enum SlideResult {
LOOP,
NO_LOOP,
NO_TRANSLATION
};
SlideResult slide(polygon_t& pA, polygon_t::ring_type& rA, polygon_t::ring_type& rB, nfp_t& nfp, const point_t& transB, bool inside) {
polygon_t::ring_type rifsB;
boost::geometry::transform(rB, rifsB, trans::translate_transformer<coord_t, 2, 2>(transB.x_, transB.y_));
rB = std::move(rifsB);
#ifdef NFP_DEBUG
write_svg("ifs.svg", pA, rB);
#endif
bool startAvailable = true;
psize_t cnt = 0;
point_t referenceStart = rB.front();
std::vector<TranslationVector> history;
//generate the nfp for the ring
while(startAvailable) {
DEBUG_VAL(cnt);
//use first point of rB as reference
nfp.back().push_back(rB.front());
if(cnt == 15)
std::cerr << "";
std::vector<TouchingPoint> touchers = findTouchingPoints(rA, rB);
#ifdef NFP_DEBUG
DEBUG_MSG("touchers", touchers.size());
for(auto t : touchers) {
DEBUG_VAL(t.type_);
}
#endif
if(touchers.empty()) {
throw std::runtime_error("Internal error: No touching points found");
}
std::vector<TranslationVector> transVectors = findFeasibleTranslationVectors(rA, rB, touchers);
#ifdef NFP_DEBUG
DEBUG_MSG("collected vectors", transVectors.size());
for(auto pt : transVectors) {
DEBUG_VAL(pt);
}
#endif
if(transVectors.empty()) {
return NO_LOOP;
}
TranslationVector next = selectNextTranslationVector(pA, rA, rB, transVectors, history);
if(next.vector_ == INVALID_POINT)
return NO_TRANSLATION;
DEBUG_MSG("next", next);
TranslationVector trimmed = trimVector(rA, rB, next);
DEBUG_MSG("trimmed", trimmed);
history.push_back(next);
polygon_t::ring_type nextRB;
boost::geometry::transform(rB, nextRB, trans::translate_transformer<coord_t, 2, 2>(trimmed.vector_.x_, trimmed.vector_.y_));
rB = std::move(nextRB);
#ifdef NFP_DEBUG
write_svg("next" + std::to_string(cnt) + ".svg", pA,rB);
#endif
++cnt;
if(referenceStart == rB.front() || (inside && bg::touches(rB.front(), nfp.front()))) {
startAvailable = false;
}
}
return LOOP;
}
void removeCoLinear(polygon_t::ring_type& r) {
assert(r.size() > 2);
psize_t nextI;
psize_t prevI = 0;
segment_t segment(r[r.size() - 2], r[0]);
polygon_t::ring_type newR;
for (psize_t i = 1; i < r.size() + 1; ++i) {
if (i >= r.size())
nextI = i % r.size() + 1;
else
nextI = i;
if (get_alignment(segment, r[nextI]) != ON) {
newR.push_back(r[prevI]);
}
segment = {segment.second, r[nextI]};
prevI = nextI;
}
r = newR;
}
void removeCoLinear(polygon_t& p) {
removeCoLinear(p.outer());
for (auto& r : p.inners())
removeCoLinear(r);
}
nfp_t generateNFP(polygon_t& pA, polygon_t& pB, const bool checkValidity = true) {
removeCoLinear(pA);
removeCoLinear(pB);
if(checkValidity) {
std::string reason;
if(!bg::is_valid(pA, reason))
throw std::runtime_error("Polygon A is invalid: " + reason);
if(!bg::is_valid(pB, reason))
throw std::runtime_error("Polygon B is invalid: " + reason);
}
nfp_t nfp;
#ifdef NFP_DEBUG
write_svg("start.svg", {pA, pB});
#endif
DEBUG_VAL(bg::wkt(pA))
DEBUG_VAL(bg::wkt(pB));
//prevent double vertex connections at start because we might come back the same way we go which would end the nfp prematurely
std::vector<psize_t> ptyaminI = find_minimum_y(pA);
std::vector<psize_t> ptybmaxI = find_maximum_y(pB);
point_t pAstart;
point_t pBstart;
if(ptyaminI.size() > 1 || ptybmaxI.size() > 1) {
//find right-most of A and left-most of B to prevent double connection at start
coord_t maxX = MIN_COORD;
psize_t iRightMost = 0;
for(psize_t& ia : ptyaminI) {
const point_t& candidateA = pA.outer()[ia];
if(larger(candidateA.x_, maxX)) {
maxX = candidateA.x_;
iRightMost = ia;
}
}
coord_t minX = MAX_COORD;
psize_t iLeftMost = 0;
for(psize_t& ib : ptybmaxI) {
const point_t& candidateB = pB.outer()[ib];
if(smaller(candidateB.x_, minX)) {
minX = candidateB.x_;
iLeftMost = ib;
}
}
pAstart = pA.outer()[iRightMost];
pBstart = pB.outer()[iLeftMost];
} else {
pAstart = pA.outer()[ptyaminI.front()];
pBstart = pB.outer()[ptybmaxI.front()];
}
nfp.push_back({});
point_t transB = {pAstart - pBstart};
if(slide(pA, pA.outer(), pB.outer(), nfp, transB, false) != LOOP) {
throw std::runtime_error("Unable to complete outer nfp loop");
}
DEBUG_VAL("##### outer #####");
point_t startTrans;
while(true) {
SearchStartResult res = searchStartTranslation(pA.outer(), pB.outer(), nfp, false, startTrans);
if(res == FOUND) {
nfp.push_back({});
DEBUG_VAL("##### interlock start #####")
polygon_t::ring_type rifsB;
boost::geometry::transform(pB.outer(), rifsB, trans::translate_transformer<coord_t, 2, 2>(startTrans.x_, startTrans.y_));
if(inNfp(rifsB.front(), nfp)) {
continue;
}
SlideResult sres = slide(pA, pA.outer(), pB.outer(), nfp, startTrans, true);
if(sres != LOOP) {
if(sres == NO_TRANSLATION) {
//no initial slide found -> jiggsaw
if(!inNfp(pB.outer().front(),nfp)) {
nfp.push_back({});
nfp.back().push_back(pB.outer().front());
}
}
}
DEBUG_VAL("##### interlock end #####");
} else if(res == FIT) {
point_t reference = pB.outer().front();
point_t translated;
trans::translate_transformer<coord_t, 2, 2> translate(startTrans.x_, startTrans.y_);
boost::geometry::transform(reference, translated, translate);
if(!inNfp(translated,nfp)) {
nfp.push_back({});
nfp.back().push_back(translated);
}
break;
} else
break;
}
for(auto& rA : pA.inners()) {
while(true) {
SearchStartResult res = searchStartTranslation(rA, pB.outer(), nfp, true, startTrans);
if(res == FOUND) {
nfp.push_back({});
DEBUG_VAL("##### hole start #####");
slide(pA, rA, pB.outer(), nfp, startTrans, true);
DEBUG_VAL("##### hole end #####");
} else if(res == FIT) {
point_t reference = pB.outer().front();
point_t translated;
trans::translate_transformer<coord_t, 2, 2> translate(startTrans.x_, startTrans.y_);
boost::geometry::transform(reference, translated, translate);
if(!inNfp(translated,nfp)) {
nfp.push_back({});
nfp.back().push_back(translated);
}
break;
} else
break;
}
}
#ifdef NFP_DEBUG
write_svg("nfp.svg", {pA,pB}, nfp);
#endif
return nfp;
}
}
#endif