/******************************************************************************* * Author : Angus Johnson * * Date : 26 October 2022 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * * Purpose : Core Clipper Library structures and functions * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef CLIPPER_CORE_H #define CLIPPER_CORE_H #include #include #include #include #include #include #include namespace Clipper2Lib { static double const PI = 3.141592653589793238; //By far the most widely used filling rules for polygons are EvenOdd //and NonZero, sometimes called Alternate and Winding respectively. //https://en.wikipedia.org/wiki/Nonzero-rule enum class FillRule { EvenOdd, NonZero, Positive, Negative }; // Point ------------------------------------------------------------------------ template struct Point { T x; T y; #ifdef USINGZ int64_t z; template inline void Init(const T2 x_ = 0, const T2 y_ = 0, const int64_t z_ = 0) { if constexpr (std::numeric_limits::is_integer && !std::numeric_limits::is_integer) { x = static_cast(std::round(x_)); y = static_cast(std::round(y_)); z = z_; } else { x = static_cast(x_); y = static_cast(y_); z = z_; } } explicit Point() : x(0), y(0), z(0) {}; template Point(const T2 x_, const T2 y_, const int64_t z_ = 0) { Init(x_, y_); z = z_; } template explicit Point(const Point& p) { Init(p.x, p.y, p.z); } Point operator * (const double scale) const { return Point(x * scale, y * scale, z); } friend std::ostream& operator<<(std::ostream& os, const Point& point) { os << point.x << "," << point.y << "," << point.z; return os; } #else template inline void Init(const T2 x_ = 0, const T2 y_ = 0) { if constexpr (std::numeric_limits::is_integer && !std::numeric_limits::is_integer) { x = static_cast(std::round(x_)); y = static_cast(std::round(y_)); } else { x = static_cast(x_); y = static_cast(y_); } } explicit Point() : x(0), y(0) {}; template Point(const T2 x_, const T2 y_) { Init(x_, y_); } template explicit Point(const Point& p) { Init(p.x, p.y); } Point operator * (const double scale) const { return Point(x * scale, y * scale); } friend std::ostream& operator<<(std::ostream& os, const Point& point) { os << point.x << "," << point.y; return os; } #endif friend bool operator==(const Point &a, const Point &b) { return a.x == b.x && a.y == b.y; } friend bool operator!=(const Point& a, const Point& b) { return !(a == b); } inline Point operator-() const { return Point(-x,-y); } inline Point operator+(const Point &b) const { return Point(x+b.x, y+b.y); } inline Point operator-(const Point &b) const { return Point(x-b.x, y-b.y); } inline void Negate() { x = -x; y = -y; } }; //nb: using 'using' here (instead of typedef) as they can be used in templates using Point64 = Point; using PointD = Point; template using Path = std::vector>; template using Paths = std::vector>; using Path64 = Path; using PathD = Path; using Paths64 = std::vector< Path64>; using PathsD = std::vector< PathD>; template std::ostream& operator << (std::ostream& outstream, const Path& path) { if (!path.empty()) { auto pt = path.cbegin(), last = path.cend() - 1; while (pt != last) outstream << *pt++ << ", "; outstream << *last << std::endl; } return outstream; } template std::ostream& operator << (std::ostream& outstream, const Paths& paths) { for (auto p : paths) outstream << p; return outstream; } template inline Path ScalePath(const Path& path, double scale) { Path result; result.reserve(path.size()); #ifdef USINGZ for (const Point& pt : path) result.push_back(Point(pt.x * scale, pt.y * scale, pt.z)); #else for (const Point& pt : path) result.push_back(Point(pt.x * scale, pt.y * scale)); #endif return result; } template inline Paths ScalePaths(const Paths& paths, double scale) { Paths result; result.reserve(paths.size()); for (const Path& path : paths) result.push_back(ScalePath(path, scale)); return result; } template inline Path TransformPath(const Path& path) { Path result; result.reserve(path.size()); std::transform(path.cbegin(), path.cend(), std::back_inserter(result), [](const Point& pt) {return Point(pt); }); return result; } template inline Paths TransformPaths(const Paths& paths) { Paths result; std::transform(paths.cbegin(), paths.cend(), std::back_inserter(result), [](const Path& path) {return TransformPath(path); }); return result; } inline PathD Path64ToPathD(const Path64& path) { return TransformPath(path); } inline PathsD Paths64ToPathsD(const Paths64& paths) { return TransformPaths(paths); } inline Path64 PathDToPath64(const PathD& path) { return TransformPath(path); } inline Paths64 PathsDToPaths64(const PathsD& paths) { return TransformPaths(paths); } template inline double Sqr(T val) { return static_cast(val) * static_cast(val); } template inline bool NearEqual(const Point& p1, const Point& p2, double max_dist_sqrd) { return Sqr(p1.x - p2.x) + Sqr(p1.y - p2.y) < max_dist_sqrd; } template inline Path StripNearEqual(const Path& path, double max_dist_sqrd, bool is_closed_path) { if (path.size() == 0) return Path(); Path result; result.reserve(path.size()); typename Path::const_iterator path_iter = path.cbegin(); Point first_pt = *path_iter++, last_pt = first_pt; result.push_back(first_pt); for (; path_iter != path.cend(); ++path_iter) { if (!NearEqual(*path_iter, last_pt, max_dist_sqrd)) { last_pt = *path_iter; result.push_back(last_pt); } } if (!is_closed_path) return result; while (result.size() > 1 && NearEqual(result.back(), first_pt, max_dist_sqrd)) result.pop_back(); return result; } template inline Paths StripNearEqual(const Paths& paths, double max_dist_sqrd, bool is_closed_path) { Paths result; result.reserve(paths.size()); for (typename Paths::const_iterator paths_citer = paths.cbegin(); paths_citer != paths.cend(); ++paths_citer) { result.push_back(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path)); } return result; } template inline Path StripDuplicates(const Path& path, bool is_closed_path) { if (path.size() == 0) return Path(); Path result; result.reserve(path.size()); typename Path::const_iterator path_iter = path.cbegin(); Point first_pt = *path_iter++, last_pt = first_pt; result.push_back(first_pt); for (; path_iter != path.cend(); ++path_iter) { if (*path_iter != last_pt) { last_pt = *path_iter; result.push_back(last_pt); } } if (!is_closed_path) return result; while (result.size() > 1 && result.back() == first_pt) result.pop_back(); return result; } template inline Paths StripDuplicates(const Paths& paths, bool is_closed_path) { Paths result; result.reserve(paths.size()); for (typename Paths::const_iterator paths_citer = paths.cbegin(); paths_citer != paths.cend(); ++paths_citer) { result.push_back(StripDuplicates(*paths_citer, is_closed_path)); } return result; } // Rect ------------------------------------------------------------------------ template struct Rect; using Rect64 = Rect; using RectD = Rect; template struct Rect { T left; T top; T right; T bottom; Rect() : left(0), top(0), right(0), bottom(0) {} Rect(T l, T t, T r, T b) : left(l), top(t), right(r), bottom(b) {} T Width() const { return right - left; } T Height() const { return bottom - top; } void Width(T width) { right = left + width; } void Height(T height) { bottom = top + height; } Point MidPoint() const { return Point((left + right) / 2, (top + bottom) / 2); } Path AsPath() const { Path result; result.reserve(4); result.push_back(Point(left, top)); result.push_back(Point(right, top)); result.push_back(Point(right, bottom)); result.push_back(Point(left, bottom)); return result; } bool Contains(const Point& pt) const { return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom; } bool Contains(const Rect& rec) const { return rec.left >= left && rec.right <= right && rec.top >= top && rec.bottom <= bottom; } void Scale(double scale) { left *= scale; top *= scale; right *= scale; bottom *= scale; } bool IsEmpty() const { return bottom <= top || right <= left; }; bool Intersects(const Rect& rec) const { return (std::max(left, rec.left) < std::min(right, rec.right)) && (std::max(top, rec.top) < std::min(bottom, rec.bottom)); }; friend std::ostream &operator<<(std::ostream &os, const Rect &rect) { os << "(" << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom << ")"; return os; } }; template inline Rect ScaleRect(const Rect& rect, double scale) { Rect result; if constexpr (std::numeric_limits::is_integer && !std::numeric_limits::is_integer) { result.left = static_cast(std::round(rect.left * scale)); result.top = static_cast(std::round(rect.top * scale)); result.right = static_cast(std::round(rect.right * scale)); result.bottom = static_cast(std::round(rect.bottom * scale)); } else { result.left = rect.left * scale; result.top = rect.top * scale; result.right = rect.right * scale; result.bottom = rect.bottom * scale; } return result; } // clipper2Exception --------------------------------------------------------- class Clipper2Exception : public std::exception { public: explicit Clipper2Exception(const char *description) : m_descr(description) {} virtual const char *what() const throw() { return m_descr.c_str(); } private: std::string m_descr; }; // Miscellaneous ------------------------------------------------------------ template inline double CrossProduct(const Point& pt1, const Point& pt2, const Point& pt3) { return (static_cast(pt2.x - pt1.x) * static_cast(pt3.y - pt2.y) - static_cast(pt2.y - pt1.y) * static_cast(pt3.x - pt2.x)); } template inline double CrossProduct(const Point& vec1, const Point& vec2) { return static_cast(vec1.y * vec2.x) - static_cast(vec2.y * vec1.x); } template inline double DotProduct(const Point& pt1, const Point& pt2, const Point& pt3) { return (static_cast(pt2.x - pt1.x) * static_cast(pt3.x - pt2.x) + static_cast(pt2.y - pt1.y) * static_cast(pt3.y - pt2.y)); } template inline double DotProduct(const Point& vec1, const Point& vec2) { return static_cast(vec1.x * vec2.x) + static_cast(vec1.y * vec2.y); } template inline double DistanceSqr(const Point pt1, const Point pt2) { return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y); } template inline double DistanceFromLineSqrd(const Point& pt, const Point& ln1, const Point& ln2) { //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) //see http://en.wikipedia.org/wiki/Perpendicular_distance double A = static_cast(ln1.y - ln2.y); double B = static_cast(ln2.x - ln1.x); double C = A * ln1.x + B * ln1.y; C = A * pt.x + B * pt.y - C; return (C * C) / (A * A + B * B); } template inline double Area(const Path& path) { size_t cnt = path.size(); if (cnt < 3) return 0.0; double a = 0.0; typename Path::const_iterator it1, it2 = path.cend() - 1, stop = it2; if (!(cnt & 1)) ++stop; for (it1 = path.cbegin(); it1 != stop;) { a += static_cast(it2->y + it1->y) * (it2->x - it1->x); it2 = it1 + 1; a += static_cast(it1->y + it2->y) * (it1->x - it2->x); it1 += 2; } if (cnt & 1) a += static_cast(it2->y + it1->y) * (it2->x - it1->x); return a * 0.5; } template inline double Area(const Paths& paths) { double a = 0.0; for (typename Paths::const_iterator paths_iter = paths.cbegin(); paths_iter != paths.cend(); ++paths_iter) { a += Area(*paths_iter); } return a; } template inline bool IsPositive(const Path& poly) { // A curve has positive orientation [and area] if a region 'R' // is on the left when traveling around the outside of 'R'. //https://mathworld.wolfram.com/CurveOrientation.html //nb: This statement is premised on using Cartesian coordinates return Area(poly) >= 0; } inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, const Point64& seg2a, const Point64& seg2b, bool inclusive = false) { if (inclusive) { double res1 = CrossProduct(seg1a, seg2a, seg2b); double res2 = CrossProduct(seg1b, seg2a, seg2b); if (res1 * res2 > 0) return false; double res3 = CrossProduct(seg2a, seg1a, seg1b); double res4 = CrossProduct(seg2b, seg1a, seg1b); if (res3 * res4 > 0) return false; return (res1 || res2 || res3 || res4); // ensures not collinear } else { double dx1 = static_cast(seg1a.x - seg1b.x); double dy1 = static_cast(seg1a.y - seg1b.y); double dx2 = static_cast(seg2a.x - seg2b.x); double dy2 = static_cast(seg2a.y - seg2b.y); return (((dy1 * (seg2a.x - seg1a.x) - dx1 * (seg2a.y - seg1a.y)) * (dy1 * (seg2b.x - seg1a.x) - dx1 * (seg2b.y - seg1a.y)) < 0) && ((dy2 * (seg1a.x - seg2a.x) - dx2 * (seg1a.y - seg2a.y)) * (dy2 * (seg1b.x - seg2a.x) - dx2 * (seg1b.y - seg2a.y)) < 0)); } } enum class PointInPolygonResult { IsOn, IsInside, IsOutside }; template inline PointInPolygonResult PointInPolygon(const Point& pt, const Path& polygon) { if (polygon.size() < 3) return PointInPolygonResult::IsOutside; int val = 0; typename Path::const_iterator start = polygon.cbegin(), cit = start; typename Path::const_iterator cend = polygon.cend(), pit = cend - 1; while (pit->y == pt.y) { if (pit == start) return PointInPolygonResult::IsOutside; --pit; } bool is_above = pit->y < pt.y; while (cit != cend) { if (is_above) { while (cit != cend && cit->y < pt.y) ++cit; if (cit == cend) break; } else { while (cit != cend && cit->y > pt.y) ++cit; if (cit == cend) break; } if (cit == start) pit = cend - 1; else pit = cit - 1; if (cit->y == pt.y) { if (cit->x == pt.x || (cit->y == pit->y && ((pt.x < pit->x) != (pt.x < cit->x)))) return PointInPolygonResult::IsOn; ++cit; continue; } if (pt.x < cit->x && pt.x < pit->x) { // we're only interested in edges crossing on the left } else if (pt.x > pit->x && pt.x > cit->x) val = 1 - val; // toggle val else { double d = CrossProduct(*pit, *cit, pt); if (d == 0) return PointInPolygonResult::IsOn; if ((d < 0) == is_above) val = 1 - val; } is_above = !is_above; ++cit; } return (val == 0) ? PointInPolygonResult::IsOutside : PointInPolygonResult::IsInside; } } // namespace #endif // CLIPPER_CORE_H