Added Clipper2 as a module from the navigation mesh generator rework pr.

This commit is contained in:
Relintai 2023-06-06 18:03:03 +02:00
parent 7ce5f939a9
commit 1d8be4224e
18 changed files with 7741 additions and 0 deletions

8
modules/clipper2/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.import
*.d
*.o
*.meta
*.obj
*.pyc
*.bc
*.os

View File

@ -0,0 +1,42 @@
# Exhaustive licensing information for files in the Godot Engine repository
# =========================================================================
#
# This file aims at documenting the copyright and license for every source
# file in the Godot Engine repository, and especially outline the files
# whose license differs from the MIT/Expat license used by Godot Engine.
#
# It is written as a machine-readable format following the debian/copyright
# specification. Globbing patterns (e.g. "Files: *") mean that they affect
# all corresponding files (also recursively in subfolders), apart from those
# with a more explicit copyright statement.
#
# Licenses are given with their debian/copyright short name (or SPDX identifier
# if no standard short name exists) and are all included in plain text at the
# end of this file (in alphabetical order).
#
# Disclaimer for thirdparty libraries:
# ------------------------------------
#
# Licensing details for thirdparty libraries in the 'thirdparty/' directory
# are given in summarized form, i.e. with only the "main" license described
# in the library's license statement. Different licenses of single files or
# code snippets in thirdparty libraries are not documented here.
# For example:
# Files: ./thirdparty/zlib/
# Copyright: 1995-2017, Jean-loup Gailly and Mark Adler
# License: Zlib
# The exact copyright for each file in that library *may* differ, and some
# files or code snippets might be distributed under other compatible licenses
# (e.g. a public domain dedication), but as far as Godot Engine is concerned
# the library is considered as a whole under the Zlib license.
#
# Nota: When linking dynamically against thirdparty libraries instead of
# building them into the Godot binary, you may remove the corresponding
# license details from this file.
-----------------------------------------------------------------------
Files: modules/clipper2/lib/*
Comment: FastNoise Library
Copyright: 2010-2023, Angus Johnson.
License: BSL-1.0

15
modules/clipper2/SCsub Normal file
View File

@ -0,0 +1,15 @@
Import('env')
module_env = env.Clone()
module_env.add_source_files(env.modules_sources,"*.cpp")
module_env.add_source_files(env.modules_sources,"lib/src/*.cpp")
# TODO this won't work as a custom module like this!
module_env.Prepend(CPPPATH=["#modules/clipper2/lib/include"])
if env.msvc:
module_env.Append(CXXFLAGS=['/std:c++17'])
else:
module_env.Append(CXXFLAGS=['-std=c++17'])

View File

@ -0,0 +1,17 @@
def get_doc_classes():
return [
]
def get_doc_path():
return "doc_classes"
def can_build(env, platform):
return True
def configure(env):
pass
def get_license_file():
return "COPYRIGHT.txt"

View File

@ -0,0 +1,187 @@
#include "thirdparty/clipper2/include/clipper2/clipper.h"
Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open) {
Vector<Vector<Vector2>> finished_polygons;
#ifdef CLIPPER_ENABLED
using namespace Clipper2Lib;
Paths64 polyon_paths_solution;
ClipType clipper_cliptype = ClipType::Union;
FillRule clipper_fillrule = FillRule::EvenOdd;
if (is_a_open) {
// Polyline with Polygon
Clipper64 clipper_64;
Paths64 polyline_paths;
Paths64 polygon_paths_b;
Path64 polyline_path_a;
Path64 polygon_path_b;
for (const Vector2 &polyline_point : p_polypath_a) {
const Point64 &point = Point64(polyline_point.x * (real_t)SCALE_FACTOR, polyline_point.y * (real_t)SCALE_FACTOR);
polyline_path_a.push_back(point);
}
polyline_paths.push_back(polyline_path_a);
for (const Vector2 &polypath_outline_point : p_polypath_b) {
const Point64 &point = Point64(polypath_outline_point.x * (real_t)SCALE_FACTOR, polypath_outline_point.y * (real_t)SCALE_FACTOR);
polygon_path_b.push_back(point);
}
polygon_paths_b.push_back(polygon_path_b);
switch (p_op) {
case OPERATION_UNION:
// not supported for polyline (in Godot)
return finished_polygons;
case OPERATION_DIFFERENCE:
clipper_cliptype = ClipType::Difference;
clipper_fillrule = FillRule::EvenOdd;
clipper_64.AddOpenSubject(polyline_paths);
clipper_64.AddClip(polygon_paths_b);
break;
case OPERATION_INTERSECTION:
clipper_cliptype = ClipType::Intersection;
clipper_fillrule = FillRule::EvenOdd;
clipper_64.AddOpenSubject(polyline_paths);
clipper_64.AddClip(polygon_paths_b);
break;
case OPERATION_XOR:
// not supported for polyline
return finished_polygons;
}
Paths64 polygon_solution, polyline_solution;
clipper_64.Execute(clipper_cliptype, clipper_fillrule, polygon_solution, polyline_solution);
polyon_paths_solution = polyline_solution;
} else {
// Polygon with Polygon
Paths64 polygon_paths;
Paths64 polygon_clip_paths;
Path64 polygon_path_a;
Path64 polygon_path_b;
for (const Vector2 &polypath_outline_point : p_polypath_a) {
const Point64 &point = Point64(polypath_outline_point.x * (real_t)SCALE_FACTOR, polypath_outline_point.y * (real_t)SCALE_FACTOR);
polygon_path_a.push_back(point);
}
polygon_paths.push_back(polygon_path_a);
for (const Vector2 &polypath_outline_point : p_polypath_b) {
const Point64 &point = Point64(polypath_outline_point.x * (real_t)SCALE_FACTOR, polypath_outline_point.y * (real_t)SCALE_FACTOR);
polygon_path_b.push_back(point);
}
polygon_clip_paths.push_back(polygon_path_b);
switch (p_op) {
case OPERATION_UNION:
clipper_cliptype = ClipType::Union;
clipper_fillrule = FillRule::NonZero;
polyon_paths_solution = Union(polygon_paths, polygon_clip_paths, clipper_fillrule);
break;
case OPERATION_DIFFERENCE:
clipper_cliptype = ClipType::Difference;
clipper_fillrule = FillRule::EvenOdd;
polyon_paths_solution = Difference(polygon_paths, polygon_clip_paths, clipper_fillrule);
break;
case OPERATION_INTERSECTION:
clipper_cliptype = ClipType::Intersection;
clipper_fillrule = FillRule::NonZero;
polyon_paths_solution = Intersect(polygon_paths, polygon_clip_paths, clipper_fillrule);
break;
case OPERATION_XOR:
clipper_cliptype = ClipType::Xor;
clipper_fillrule = FillRule::NonZero;
polyon_paths_solution = Xor(polygon_paths, polygon_clip_paths, clipper_fillrule);
break;
}
}
for (const Path64 &polyon_path : polyon_paths_solution) {
Vector<Vector2> finished_polygon;
for (const Point64 &polyon_path_point : polyon_path) {
finished_polygon.push_back(Vector2(static_cast<real_t>(polyon_path_point.x), static_cast<real_t>(polyon_path_point.y)) / (real_t)SCALE_FACTOR);
}
finished_polygons.push_back(finished_polygon);
}
#endif // CLIPPER_ENABLED
return finished_polygons;
}
Vector<Vector<Point2>> Geometry2D::_polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
Vector<Vector<Vector2>> finished_polygons;
#ifdef CLIPPER_ENABLED
using namespace Clipper2Lib;
JoinType clipper_jointype = JoinType::Miter;
switch (p_join_type) {
case JOIN_SQUARE:
clipper_jointype = JoinType::Square;
break;
case JOIN_ROUND:
clipper_jointype = JoinType::Round;
break;
case JOIN_MITER:
clipper_jointype = JoinType::Miter;
break;
}
EndType clipper_endtype = EndType::Polygon;
switch (p_end_type) {
case END_POLYGON:
clipper_endtype = EndType::Polygon;
break;
case END_JOINED:
clipper_endtype = EndType::Joined;
break;
case END_BUTT:
clipper_endtype = EndType::Butt;
break;
case END_SQUARE:
clipper_endtype = EndType::Square;
break;
case END_ROUND:
clipper_endtype = EndType::Round;
break;
}
Paths64 polygon_paths;
Path64 polygon_path;
for (const Vector2 &polypath_outline_point : p_polypath) {
const Point64 &point = Point64(polypath_outline_point.x * (real_t)SCALE_FACTOR, polypath_outline_point.y * (real_t)SCALE_FACTOR);
polygon_path.push_back(point);
}
polygon_paths.push_back(polygon_path);
Paths64 paths_solution = InflatePaths(polygon_paths, p_delta, clipper_jointype, clipper_endtype);
for (const Path64 &scaled_path : paths_solution) {
Vector<Vector2> polypath;
for (const Point64 &scaled_point : scaled_path) {
polypath.push_back(Vector2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y)) / (real_t)SCALE_FACTOR);
}
finished_polygons.push_back(polypath);
}
#endif // CLIPPER_ENABLED
return finished_polygons;
}

View File

@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,808 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 28 January 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* 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 <cstdlib>
#include <cmath>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <climits>
namespace Clipper2Lib
{
#if __cpp_exceptions
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;
};
static const char* precision_error =
"Precision exceeds the permitted range";
static const char* range_error =
"Values exceed permitted range";
#endif
// error codes (2^n)
const int precision_error_i = 1; // non-fatal
const int range_error_i = 2;
static const double PI = 3.141592653589793238;
static const int64_t MAX_COORD = INT64_MAX >> 2;
static const int64_t MIN_COORD = -MAX_COORD;
static const int64_t INVALID = INT64_MAX;
static const double MAX_DBL = (std::numeric_limits<double>::max)();
static const double MIN_DBL = (std::numeric_limits<double>::min)();
//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 <typename T>
struct Point {
T x;
T y;
#ifdef USINGZ
int64_t z;
template <typename T2>
inline void Init(const T2 x_ = 0, const T2 y_ = 0, const int64_t z_ = 0)
{
if constexpr (std::numeric_limits<T>::is_integer &&
!std::numeric_limits<T2>::is_integer)
{
x = static_cast<T>(std::round(x_));
y = static_cast<T>(std::round(y_));
z = z_;
}
else
{
x = static_cast<T>(x_);
y = static_cast<T>(y_);
z = z_;
}
}
explicit Point() : x(0), y(0), z(0) {};
template <typename T2>
Point(const T2 x_, const T2 y_, const int64_t z_ = 0)
{
Init(x_, y_);
z = z_;
}
template <typename T2>
explicit Point<T>(const Point<T2>& 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 <typename T2>
inline void Init(const T2 x_ = 0, const T2 y_ = 0)
{
if constexpr (std::numeric_limits<T>::is_integer &&
!std::numeric_limits<T2>::is_integer)
{
x = static_cast<T>(std::round(x_));
y = static_cast<T>(std::round(y_));
}
else
{
x = static_cast<T>(x_);
y = static_cast<T>(y_);
}
}
explicit Point() : x(0), y(0) {};
template <typename T2>
Point(const T2 x_, const T2 y_) { Init(x_, y_); }
template <typename T2>
explicit Point<T>(const Point<T2>& 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<T> operator-() const
{
return Point<T>(-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<int64_t>;
using PointD = Point<double>;
template <typename T>
using Path = std::vector<Point<T>>;
template <typename T>
using Paths = std::vector<Path<T>>;
using Path64 = Path<int64_t>;
using PathD = Path<double>;
using Paths64 = std::vector< Path64>;
using PathsD = std::vector< PathD>;
// Rect ------------------------------------------------------------------------
template <typename T>
struct Rect;
using Rect64 = Rect<int64_t>;
using RectD = Rect<double>;
template <typename T>
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<T> MidPoint() const
{
return Point<T>((left + right) / 2, (top + bottom) / 2);
}
Path<T> AsPath() const
{
Path<T> result;
result.reserve(4);
result.push_back(Point<T>(left, top));
result.push_back(Point<T>(right, top));
result.push_back(Point<T>(right, bottom));
result.push_back(Point<T>(left, bottom));
return result;
}
bool Contains(const Point<T>& pt) const
{
return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom;
}
bool Contains(const Rect<T>& 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<T>& 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<T>& rect) {
os << "("
<< rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom
<< ")";
return os;
}
};
template <typename T1, typename T2>
inline Rect<T1> ScaleRect(const Rect<T2>& rect, double scale)
{
Rect<T1> result;
if constexpr (std::numeric_limits<T1>::is_integer &&
!std::numeric_limits<T2>::is_integer)
{
result.left = static_cast<T1>(std::round(rect.left * scale));
result.top = static_cast<T1>(std::round(rect.top * scale));
result.right = static_cast<T1>(std::round(rect.right * scale));
result.bottom = static_cast<T1>(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;
}
static const Rect64 MaxInvalidRect64 = Rect64(
INT64_MAX, INT64_MAX, INT64_MIN, INT64_MIN);
static const RectD MaxInvalidRectD = RectD(
MAX_DBL, MAX_DBL, MIN_DBL, MIN_DBL);
inline Rect64 Bounds(const Path64& path)
{
Rect64 rec = MaxInvalidRect64;
for (const Point64& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return Rect64();
return rec;
}
inline Rect64 Bounds(const Paths64& paths)
{
Rect64 rec = MaxInvalidRect64;
for (const Path64& path : paths)
for (const Point64& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return Rect64();
return rec;
}
inline RectD Bounds(const PathD& path)
{
RectD rec = MaxInvalidRectD;
for (const PointD& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return RectD();
return rec;
}
inline RectD Bounds(const PathsD& paths)
{
RectD rec = MaxInvalidRectD;
for (const PathD& path : paths)
for (const PointD& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return RectD();
return rec;
}
template <typename T>
std::ostream& operator << (std::ostream& outstream, const Path<T>& path)
{
if (!path.empty())
{
auto pt = path.cbegin(), last = path.cend() - 1;
while (pt != last)
outstream << *pt++ << ", ";
outstream << *last << std::endl;
}
return outstream;
}
template <typename T>
std::ostream& operator << (std::ostream& outstream, const Paths<T>& paths)
{
for (auto p : paths)
outstream << p;
return outstream;
}
template <typename T1, typename T2>
inline Path<T1> ScalePath(const Path<T2>& path, double scale_x, double scale_y)
{
Path<T1> result;
result.reserve(path.size());
#ifdef USINGZ
std::transform(path.begin(), path.end(), back_inserter(result),
[scale_x, scale_y](const auto& pt)
{ return Point<T1>(pt.x * scale_x, pt.y * scale_y, pt.z); });
#else
std::transform(path.begin(), path.end(), back_inserter(result),
[scale_x, scale_y](const auto& pt)
{ return Point<T1>(pt.x * scale_x, pt.y * scale_y); });
#endif
return result;
}
template <typename T1, typename T2>
inline Path<T1> ScalePath(const Path<T2>& path, double scale)
{
return ScalePath<T1, T2>(path, scale, scale);
}
template <typename T1, typename T2>
inline Paths<T1> ScalePaths(const Paths<T2>& paths,
double scale_x, double scale_y, int& error_code)
{
Paths<T1> result;
if constexpr (std::numeric_limits<T1>::is_integer &&
!std::numeric_limits<T2>::is_integer)
{
RectD r = Bounds(paths);
const double max_coord_d = static_cast<double>(MAX_COORD);
const double min_coord_d = static_cast<double>(MIN_COORD);
if ((r.left * scale_x) < min_coord_d ||
(r.right * scale_x) > max_coord_d ||
(r.top * scale_y) < min_coord_d ||
(r.bottom * scale_y) > max_coord_d)
{
error_code |= range_error_i;
#if __cpp_exceptions
throw Clipper2Exception(range_error);
#else
// error_code = range_error_i; // compiler complains if here
return result; // empty
#endif
}
}
result.reserve(paths.size());
std::transform(paths.begin(), paths.end(), back_inserter(result),
[scale_x, scale_y](const auto& path)
{ return ScalePath<T1, T2>(path, scale_x, scale_y); });
return result;
}
template <typename T1, typename T2>
inline Paths<T1> ScalePaths(const Paths<T2>& paths,
double scale, int& error_code)
{
return ScalePaths<T1, T2>(paths, scale, scale, error_code);
}
template <typename T1, typename T2>
inline Path<T1> TransformPath(const Path<T2>& path)
{
Path<T1> result;
result.reserve(path.size());
std::transform(path.cbegin(), path.cend(), std::back_inserter(result),
[](const Point<T2>& pt) {return Point<T1>(pt); });
return result;
}
template <typename T1, typename T2>
inline Paths<T1> TransformPaths(const Paths<T2>& paths)
{
Paths<T1> result;
std::transform(paths.cbegin(), paths.cend(), std::back_inserter(result),
[](const Path<T2>& path) {return TransformPath<T1, T2>(path); });
return result;
}
inline PathD Path64ToPathD(const Path64& path)
{
return TransformPath<double, int64_t>(path);
}
inline PathsD Paths64ToPathsD(const Paths64& paths)
{
return TransformPaths<double, int64_t>(paths);
}
inline Path64 PathDToPath64(const PathD& path)
{
return TransformPath<int64_t, double>(path);
}
inline Paths64 PathsDToPaths64(const PathsD& paths)
{
return TransformPaths<int64_t, double>(paths);
}
template<typename T>
inline double Sqr(T val)
{
return static_cast<double>(val) * static_cast<double>(val);
}
template<typename T>
inline bool NearEqual(const Point<T>& p1,
const Point<T>& p2, double max_dist_sqrd)
{
return Sqr(p1.x - p2.x) + Sqr(p1.y - p2.y) < max_dist_sqrd;
}
template<typename T>
inline Path<T> StripNearEqual(const Path<T>& path,
double max_dist_sqrd, bool is_closed_path)
{
if (path.size() == 0) return Path<T>();
Path<T> result;
result.reserve(path.size());
typename Path<T>::const_iterator path_iter = path.cbegin();
Point<T> 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<typename T>
inline Paths<T> StripNearEqual(const Paths<T>& paths,
double max_dist_sqrd, bool is_closed_path)
{
Paths<T> result;
result.reserve(paths.size());
for (typename Paths<T>::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<typename T>
inline Path<T> StripDuplicates(const Path<T>& path, bool is_closed_path)
{
if (path.size() == 0) return Path<T>();
Path<T> result;
result.reserve(path.size());
typename Path<T>::const_iterator path_iter = path.cbegin();
Point<T> 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<typename T>
inline Paths<T> StripDuplicates(const Paths<T>& paths, bool is_closed_path)
{
Paths<T> result;
result.reserve(paths.size());
for (typename Paths<T>::const_iterator paths_citer = paths.cbegin();
paths_citer != paths.cend(); ++paths_citer)
{
result.push_back(StripDuplicates(*paths_citer, is_closed_path));
}
return result;
}
// Miscellaneous ------------------------------------------------------------
inline void CheckPrecision(int& precision, int& error_code)
{
if (precision >= -8 && precision <= 8) return;
error_code |= precision_error_i; // non-fatal error
#if __cpp_exceptions
throw Clipper2Exception(precision_error);
#else
precision = precision > 8 ? 8 : -8;
#endif
}
inline void CheckPrecision(int& precision)
{
int error_code = 0;
CheckPrecision(precision, error_code);
}
template <typename T>
inline double CrossProduct(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3) {
return (static_cast<double>(pt2.x - pt1.x) * static_cast<double>(pt3.y -
pt2.y) - static_cast<double>(pt2.y - pt1.y) * static_cast<double>(pt3.x - pt2.x));
}
template <typename T>
inline double CrossProduct(const Point<T>& vec1, const Point<T>& vec2)
{
return static_cast<double>(vec1.y * vec2.x) - static_cast<double>(vec2.y * vec1.x);
}
template <typename T>
inline double DotProduct(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3) {
return (static_cast<double>(pt2.x - pt1.x) * static_cast<double>(pt3.x - pt2.x) +
static_cast<double>(pt2.y - pt1.y) * static_cast<double>(pt3.y - pt2.y));
}
template <typename T>
inline double DotProduct(const Point<T>& vec1, const Point<T>& vec2)
{
return static_cast<double>(vec1.x * vec2.x) + static_cast<double>(vec1.y * vec2.y);
}
template <typename T>
inline double DistanceSqr(const Point<T> pt1, const Point<T> pt2)
{
return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y);
}
template <typename T>
inline double DistanceFromLineSqrd(const Point<T>& pt, const Point<T>& ln1, const Point<T>& 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<double>(ln1.y - ln2.y);
double B = static_cast<double>(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 <typename T>
inline double Area(const Path<T>& path)
{
size_t cnt = path.size();
if (cnt < 3) return 0.0;
double a = 0.0;
typename Path<T>::const_iterator it1, it2 = path.cend() - 1, stop = it2;
if (!(cnt & 1)) ++stop;
for (it1 = path.cbegin(); it1 != stop;)
{
a += static_cast<double>(it2->y + it1->y) * (it2->x - it1->x);
it2 = it1 + 1;
a += static_cast<double>(it1->y + it2->y) * (it1->x - it2->x);
it1 += 2;
}
if (cnt & 1)
a += static_cast<double>(it2->y + it1->y) * (it2->x - it1->x);
return a * 0.5;
}
template <typename T>
inline double Area(const Paths<T>& paths)
{
double a = 0.0;
for (typename Paths<T>::const_iterator paths_iter = paths.cbegin();
paths_iter != paths.cend(); ++paths_iter)
{
a += Area<T>(*paths_iter);
}
return a;
}
template <typename T>
inline bool IsPositive(const Path<T>& 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<T>(poly) >= 0;
}
static const double max_coord = static_cast<double>(MAX_COORD);
static const double min_coord = static_cast<double>(MIN_COORD);
inline int64_t CheckCastInt64(double val)
{
if ((val >= max_coord) || (val <= min_coord)) return INVALID;
else return static_cast<int64_t>(val);
}
inline bool GetIntersectPoint(const Point64& ln1a, const Point64& ln1b,
const Point64& ln2a, const Point64& ln2b, Point64& ip)
{
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
double dx1 = static_cast<double>(ln1b.x - ln1a.x);
double dy1 = static_cast<double>(ln1b.y - ln1a.y);
double dx2 = static_cast<double>(ln2b.x - ln2a.x);
double dy2 = static_cast<double>(ln2b.y - ln2a.y);
double det = dy1 * dx2 - dy2 * dx1;
if (det == 0.0) return 0;
double qx = dx1 * ln1a.y - dy1 * ln1a.x;
double qy = dx2 * ln2a.y - dy2 * ln2a.x;
ip.x = CheckCastInt64((dx1 * qy - dx2 * qx) / det);
ip.y = CheckCastInt64((dy1 * qy - dy2 * qx) / det);
return (ip.x != INVALID && ip.y != INVALID);
}
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 {
return (CrossProduct(seg1a, seg2a, seg2b) *
CrossProduct(seg1b, seg2a, seg2b) < 0) &&
(CrossProduct(seg2a, seg1a, seg1b) *
CrossProduct(seg2b, seg1a, seg1b) < 0);
}
}
inline Point64 GetClosestPointOnSegment(const Point64& offPt,
const Point64& seg1, const Point64& seg2)
{
if (seg1.x == seg2.x && seg1.y == seg2.y) return seg1;
double dx = static_cast<double>(seg2.x - seg1.x);
double dy = static_cast<double>(seg2.y - seg1.y);
double q =
(static_cast<double>(offPt.x - seg1.x) * dx +
static_cast<double>(offPt.y - seg1.y) * dy) /
(Sqr(dx) + Sqr(dy));
if (q < 0) q = 0; else if (q > 1) q = 1;
return Point64(
seg1.x + static_cast<int64_t>(nearbyint(q * dx)),
seg1.y + static_cast<int64_t>(nearbyint(q * dy)));
}
enum class PointInPolygonResult { IsOn, IsInside, IsOutside };
template <typename T>
inline PointInPolygonResult PointInPolygon(const Point<T>& pt, const Path<T>& polygon)
{
if (polygon.size() < 3)
return PointInPolygonResult::IsOutside;
int val = 0;
typename Path<T>::const_iterator start = polygon.cbegin(), cit = start;
typename Path<T>::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

View File

@ -0,0 +1,610 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 28 January 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : This is the main polygon clipping module *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_ENGINE_H
#define CLIPPER_ENGINE_H
constexpr auto CLIPPER2_VERSION = "1.1.0";
#include <cstdlib>
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
#include <numeric>
#include <memory>
#include "clipper.core.h"
namespace Clipper2Lib {
struct Scanline;
struct IntersectNode;
struct Active;
struct Vertex;
struct LocalMinima;
struct OutRec;
struct HorzSegment;
//Note: all clipping operations except for Difference are commutative.
enum class ClipType { None, Intersection, Union, Difference, Xor };
enum class PathType { Subject, Clip };
enum class JoinWith { None, Left, Right };
enum class VertexFlags : uint32_t {
None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8
};
constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b)
{
return (enum VertexFlags)(uint32_t(a) & uint32_t(b));
}
constexpr enum VertexFlags operator |(enum VertexFlags a, enum VertexFlags b)
{
return (enum VertexFlags)(uint32_t(a) | uint32_t(b));
}
struct Vertex {
Point64 pt;
Vertex* next = nullptr;
Vertex* prev = nullptr;
VertexFlags flags = VertexFlags::None;
};
struct OutPt {
Point64 pt;
OutPt* next = nullptr;
OutPt* prev = nullptr;
OutRec* outrec;
HorzSegment* horz = nullptr;
OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) {
next = this;
prev = this;
}
};
class PolyPath;
class PolyPath64;
class PolyPathD;
using PolyTree64 = PolyPath64;
using PolyTreeD = PolyPathD;
struct OutRec;
typedef std::vector<OutRec*> OutRecList;
//OutRec: contains a path in the clipping solution. Edges in the AEL will
//have OutRec pointers assigned when they form part of the clipping solution.
struct OutRec {
size_t idx = 0;
OutRec* owner = nullptr;
Active* front_edge = nullptr;
Active* back_edge = nullptr;
OutPt* pts = nullptr;
PolyPath* polypath = nullptr;
OutRecList* splits = nullptr;
Rect64 bounds = {};
Path64 path;
bool is_open = false;
bool horz_done = false;
~OutRec() {
if (splits) delete splits;
// nb: don't delete the split pointers
// as these are owned by ClipperBase's outrec_list_
};
};
///////////////////////////////////////////////////////////////////
//Important: UP and DOWN here are premised on Y-axis positive down
//displays, which is the orientation used in Clipper's development.
///////////////////////////////////////////////////////////////////
struct Active {
Point64 bot;
Point64 top;
int64_t curr_x = 0; //current (updated at every new scanline)
double dx = 0.0;
int wind_dx = 1; //1 or -1 depending on winding direction
int wind_cnt = 0;
int wind_cnt2 = 0; //winding count of the opposite polytype
OutRec* outrec = nullptr;
//AEL: 'active edge list' (Vatti's AET - active edge table)
// a linked list of all edges (from left to right) that are present
// (or 'active') within the current scanbeam (a horizontal 'beam' that
// sweeps from bottom to top over the paths in the clipping operation).
Active* prev_in_ael = nullptr;
Active* next_in_ael = nullptr;
//SEL: 'sorted edge list' (Vatti's ST - sorted table)
// linked list used when sorting edges into their new positions at the
// top of scanbeams, but also (re)used to process horizontals.
Active* prev_in_sel = nullptr;
Active* next_in_sel = nullptr;
Active* jump = nullptr;
Vertex* vertex_top = nullptr;
LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti)
bool is_left_bound = false;
JoinWith join_with = JoinWith::None;
};
struct LocalMinima {
Vertex* vertex;
PathType polytype;
bool is_open;
LocalMinima(Vertex* v, PathType pt, bool open) :
vertex(v), polytype(pt), is_open(open){}
};
struct IntersectNode {
Point64 pt;
Active* edge1;
Active* edge2;
IntersectNode() : pt(Point64(0,0)), edge1(NULL), edge2(NULL) {}
IntersectNode(Active* e1, Active* e2, Point64& pt_) :
pt(pt_), edge1(e1), edge2(e2) {}
};
struct HorzSegment {
OutPt* left_op;
OutPt* right_op = nullptr;
bool left_to_right = true;
HorzSegment() : left_op(nullptr) { }
explicit HorzSegment(OutPt* op) : left_op(op) { }
};
struct HorzJoin {
OutPt* op1 = nullptr;
OutPt* op2 = nullptr;
HorzJoin() {};
explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { }
};
#ifdef USINGZ
typedef std::function<void(const Point64& e1bot, const Point64& e1top,
const Point64& e2bot, const Point64& e2top, Point64& pt)> ZCallback64;
typedef std::function<void(const PointD& e1bot, const PointD& e1top,
const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD;
#endif
typedef std::vector<HorzSegment> HorzSegmentList;
typedef std::unique_ptr<LocalMinima> LocalMinima_ptr;
typedef std::vector<LocalMinima_ptr> LocalMinimaList;
typedef std::vector<IntersectNode> IntersectNodeList;
// ClipperBase -------------------------------------------------------------
class ClipperBase {
private:
ClipType cliptype_ = ClipType::None;
FillRule fillrule_ = FillRule::EvenOdd;
FillRule fillpos = FillRule::Positive;
int64_t bot_y_ = 0;
bool minima_list_sorted_ = false;
bool using_polytree_ = false;
Active* actives_ = nullptr;
Active *sel_ = nullptr;
LocalMinimaList minima_list_; //pointers in case of memory reallocs
LocalMinimaList::iterator current_locmin_iter_;
std::vector<Vertex*> vertex_lists_;
std::priority_queue<int64_t> scanline_list_;
IntersectNodeList intersect_nodes_;
HorzSegmentList horz_seg_list_;
std::vector<HorzJoin> horz_join_list_;
void Reset();
inline void InsertScanline(int64_t y);
inline bool PopScanline(int64_t &y);
inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima);
void DisposeAllOutRecs();
void DisposeVerticesAndLocalMinima();
void DeleteEdges(Active*& e);
inline void AddLocMin(Vertex &vert, PathType polytype, bool is_open);
bool IsContributingClosed(const Active &e) const;
inline bool IsContributingOpen(const Active &e) const;
void SetWindCountForClosedPathEdge(Active &edge);
void SetWindCountForOpenPathEdge(Active &e);
void InsertLocalMinimaIntoAEL(int64_t bot_y);
void InsertLeftEdge(Active &e);
inline void PushHorz(Active &e);
inline bool PopHorz(Active *&e);
inline OutPt* StartOpenPath(Active &e, const Point64& pt);
inline void UpdateEdgeIntoAEL(Active *e);
OutPt* IntersectEdges(Active &e1, Active &e2, const Point64& pt);
inline void DeleteFromAEL(Active &e);
inline void AdjustCurrXAndCopyToSEL(const int64_t top_y);
void DoIntersections(const int64_t top_y);
void AddNewIntersectNode(Active &e1, Active &e2, const int64_t top_y);
bool BuildIntersectList(const int64_t top_y);
void ProcessIntersectList();
void SwapPositionsInAEL(Active& edge1, Active& edge2);
OutRec* NewOutRec();
OutPt* AddOutPt(const Active &e, const Point64& pt);
OutPt* AddLocalMinPoly(Active &e1, Active &e2,
const Point64& pt, bool is_new = false);
OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt);
void DoHorizontal(Active &horz);
bool ResetHorzDirection(const Active &horz, const Vertex* max_vertex,
int64_t &horz_left, int64_t &horz_right);
void DoTopOfScanbeam(const int64_t top_y);
Active *DoMaxima(Active &e);
void JoinOutrecPaths(Active &e1, Active &e2);
void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec);
void FixSelfIntersects(OutRec* outrec);
void DoSplitOp(OutRec* outRec, OutPt* splitOp);
inline void AddTrialHorzJoin(OutPt* op);
void ConvertHorzSegsToJoins();
void ProcessHorzJoins();
void Split(Active& e, const Point64& pt);
inline void CheckJoinLeft(Active& e,
const Point64& pt, bool check_curr_x = false);
inline void CheckJoinRight(Active& e,
const Point64& pt, bool check_curr_x = false);
protected:
int error_code_ = 0;
bool has_open_paths_ = false;
bool succeeded_ = true;
OutRecList outrec_list_; //pointers in case list memory reallocated
bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
void CleanCollinear(OutRec* outrec);
bool CheckBounds(OutRec* outrec);
void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath);
void DeepCheckOwners(OutRec* outrec, PolyPath* polypath);
#ifdef USINGZ
ZCallback64 zCallback_ = nullptr;
void SetZ(const Active& e1, const Active& e2, Point64& pt);
#endif
void CleanUp(); // unlike Clear, CleanUp preserves added paths
void AddPath(const Path64& path, PathType polytype, bool is_open);
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
public:
virtual ~ClipperBase();
int ErrorCode() { return error_code_; };
bool PreserveCollinear = true;
bool ReverseSolution = false;
void Clear();
#ifdef USINGZ
int64_t DefaultZ = 0;
#endif
};
// PolyPath / PolyTree --------------------------------------------------------
//PolyTree: is intended as a READ-ONLY data structure for CLOSED paths returned
//by clipping operations. While this structure is more complex than the
//alternative Paths structure, it does preserve path 'ownership' - ie those
//paths that contain (or own) other paths. This will be useful to some users.
class PolyPath {
protected:
PolyPath* parent_;
public:
PolyPath(PolyPath* parent = nullptr): parent_(parent){}
virtual ~PolyPath() {};
//https://en.cppreference.com/w/cpp/language/rule_of_three
PolyPath(const PolyPath&) = delete;
PolyPath& operator=(const PolyPath&) = delete;
unsigned Level() const
{
unsigned result = 0;
const PolyPath* p = parent_;
while (p) { ++result; p = p->parent_; }
return result;
}
virtual PolyPath* AddChild(const Path64& path) = 0;
virtual void Clear() = 0;
virtual size_t Count() const { return 0; }
const PolyPath* Parent() const { return parent_; }
bool IsHole() const
{
unsigned lvl = Level();
//Even levels except level 0
return lvl && !(lvl & 1);
}
};
typedef typename std::vector<std::unique_ptr<PolyPath64>> PolyPath64List;
typedef typename std::vector<std::unique_ptr<PolyPathD>> PolyPathDList;
class PolyPath64 : public PolyPath {
private:
PolyPath64List childs_;
Path64 polygon_;
public:
explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
~PolyPath64() {
childs_.resize(0);
}
const PolyPath64* operator [] (size_t index) const
{
return childs_[index].get();
}
const PolyPath64* Child(size_t index) const
{
return childs_[index].get();
}
PolyPath64List::const_iterator begin() const { return childs_.cbegin(); }
PolyPath64List::const_iterator end() const { return childs_.cend(); }
PolyPath64* AddChild(const Path64& path) override
{
auto p = std::make_unique<PolyPath64>(this);
auto* result = childs_.emplace_back(std::move(p)).get();
result->polygon_ = path;
return result;
}
void Clear() override
{
childs_.resize(0);
}
size_t Count() const override
{
return childs_.size();
}
const Path64& Polygon() const { return polygon_; };
double Area() const
{
return std::accumulate(childs_.cbegin(), childs_.cend(),
Clipper2Lib::Area<int64_t>(polygon_),
[](double a, const auto& child) {return a + child->Area(); });
}
};
class PolyPathD : public PolyPath {
private:
PolyPathDList childs_;
double inv_scale_;
PathD polygon_;
public:
explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent)
{
inv_scale_ = parent ? parent->inv_scale_ : 1.0;
}
~PolyPathD() {
childs_.resize(0);
}
const PolyPathD* operator [] (size_t index) const
{
return childs_[index].get();
}
const PolyPathD* Child(size_t index) const
{
return childs_[index].get();
}
PolyPathDList::const_iterator begin() const { return childs_.cbegin(); }
PolyPathDList::const_iterator end() const { return childs_.cend(); }
void SetInvScale(double value) { inv_scale_ = value; }
double InvScale() { return inv_scale_; }
PolyPathD* AddChild(const Path64& path) override
{
int error_code = 0;
auto p = std::make_unique<PolyPathD>(this);
PolyPathD* result = childs_.emplace_back(std::move(p)).get();
result->polygon_ = ScalePath<double, int64_t>(path, inv_scale_, error_code);
return result;
}
void Clear() override
{
childs_.resize(0);
}
size_t Count() const override
{
return childs_.size();
}
const PathD& Polygon() const { return polygon_; };
double Area() const
{
return std::accumulate(childs_.begin(), childs_.end(),
Clipper2Lib::Area<double>(polygon_),
[](double a, const auto& child) {return a + child->Area(); });
}
};
class Clipper64 : public ClipperBase
{
private:
void BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen);
void BuildTree64(PolyPath64& polytree, Paths64& open_paths);
public:
#ifdef USINGZ
void SetZCallback(ZCallback64 cb) { zCallback_ = cb; }
#endif
void AddSubject(const Paths64& subjects)
{
AddPaths(subjects, PathType::Subject, false);
}
void AddOpenSubject(const Paths64& open_subjects)
{
AddPaths(open_subjects, PathType::Subject, true);
}
void AddClip(const Paths64& clips)
{
AddPaths(clips, PathType::Clip, false);
}
bool Execute(ClipType clip_type,
FillRule fill_rule, Paths64& closed_paths)
{
Paths64 dummy;
return Execute(clip_type, fill_rule, closed_paths, dummy);
}
bool Execute(ClipType clip_type, FillRule fill_rule,
Paths64& closed_paths, Paths64& open_paths)
{
closed_paths.clear();
open_paths.clear();
if (ExecuteInternal(clip_type, fill_rule, false))
BuildPaths64(closed_paths, &open_paths);
CleanUp();
return succeeded_;
}
bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree)
{
Paths64 dummy;
return Execute(clip_type, fill_rule, polytree, dummy);
}
bool Execute(ClipType clip_type,
FillRule fill_rule, PolyTree64& polytree, Paths64& open_paths)
{
if (ExecuteInternal(clip_type, fill_rule, true))
{
open_paths.clear();
polytree.Clear();
BuildTree64(polytree, open_paths);
}
CleanUp();
return succeeded_;
}
};
class ClipperD : public ClipperBase {
private:
double scale_ = 1.0, invScale_ = 1.0;
#ifdef USINGZ
ZCallbackD zCallbackD_ = nullptr;
#endif
void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen);
void BuildTreeD(PolyPathD& polytree, PathsD& open_paths);
public:
explicit ClipperD(int precision = 2) : ClipperBase()
{
CheckPrecision(precision, error_code_);
// to optimize scaling / descaling precision
// set the scale to a power of double's radix (2) (#25)
scale_ = std::pow(std::numeric_limits<double>::radix,
std::ilogb(std::pow(10, precision)) + 1);
invScale_ = 1 / scale_;
}
#ifdef USINGZ
void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; };
void ZCB(const Point64& e1bot, const Point64& e1top,
const Point64& e2bot, const Point64& e2top, Point64& pt)
{
// de-scale (x & y)
// temporarily convert integers to their initial float values
// this will slow clipping marginally but will make it much easier
// to understand the coordinates passed to the callback function
PointD tmp = PointD(pt) * invScale_;
PointD e1b = PointD(e1bot) * invScale_;
PointD e1t = PointD(e1top) * invScale_;
PointD e2b = PointD(e2bot) * invScale_;
PointD e2t = PointD(e2top) * invScale_;
zCallbackD_(e1b,e1t, e2b, e2t, tmp);
pt.z = tmp.z; // only update 'z'
};
void CheckCallback()
{
if(zCallbackD_)
// if the user defined float point callback has been assigned
// then assign the proxy callback function
ClipperBase::zCallback_ =
std::bind(&ClipperD::ZCB, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4, std::placeholders::_5);
else
ClipperBase::zCallback_ = nullptr;
}
#endif
void AddSubject(const PathsD& subjects)
{
AddPaths(ScalePaths<int64_t, double>(subjects, scale_, error_code_), PathType::Subject, false);
}
void AddOpenSubject(const PathsD& open_subjects)
{
AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_, error_code_), PathType::Subject, true);
}
void AddClip(const PathsD& clips)
{
AddPaths(ScalePaths<int64_t, double>(clips, scale_, error_code_), PathType::Clip, false);
}
bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths)
{
PathsD dummy;
return Execute(clip_type, fill_rule, closed_paths, dummy);
}
bool Execute(ClipType clip_type,
FillRule fill_rule, PathsD& closed_paths, PathsD& open_paths)
{
#ifdef USINGZ
CheckCallback();
#endif
if (ExecuteInternal(clip_type, fill_rule, false))
{
BuildPathsD(closed_paths, &open_paths);
}
CleanUp();
return succeeded_;
}
bool Execute(ClipType clip_type, FillRule fill_rule, PolyTreeD& polytree)
{
PathsD dummy;
return Execute(clip_type, fill_rule, polytree, dummy);
}
bool Execute(ClipType clip_type,
FillRule fill_rule, PolyTreeD& polytree, PathsD& open_paths)
{
#ifdef USINGZ
CheckCallback();
#endif
if (ExecuteInternal(clip_type, fill_rule, true))
{
polytree.Clear();
polytree.SetInvScale(invScale_);
open_paths.clear();
BuildTreeD(polytree, open_paths);
}
CleanUp();
return succeeded_;
}
};
} // namespace
#endif // CLIPPER_ENGINE_H

View File

@ -0,0 +1,830 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 11 December 2022 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 *
* Purpose : This module exports the Clipper2 Library (ie DLL/so) *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
// The exported functions below refer to simple structures that
// can be understood across multiple languages. Consequently
// Path64, PathD, Polytree64 etc are converted from C++ classes
// (std::vector<> etc) into the following data structures:
//
// CPath64 (int64_t*) & CPathD (double_t*):
// Path64 and PathD are converted into arrays of x,y coordinates.
// However in these arrays the first x,y coordinate pair is a
// counter with 'x' containing the number of following coordinate
// pairs. ('y' should be 0, with one exception explained below.)
// __________________________________
// |counter|coord1|coord2|...|coordN|
// |N ,0 |x1, y1|x2, y2|...|xN, yN|
// __________________________________
//
// CPaths64 (int64_t**) & CPathsD (double_t**):
// These are arrays of pointers to CPath64 and CPathD where
// the first pointer is to a 'counter path'. This 'counter
// path' has a single x,y coord pair with 'y' (not 'x')
// containing the number of paths that follow. ('x' = 0).
// _______________________________
// |counter|path1|path2|...|pathN|
// |addr0 |addr1|addr2|...|addrN| (*addr0[0]=0; *addr0[1]=N)
// _______________________________
//
// The structures of CPolytree64 and CPolytreeD are defined
// below and these structures don't need to be explained here.
#ifndef CLIPPER2_EXPORT_H
#define CLIPPER2_EXPORT_H
#include <cstdlib>
#include <vector>
#include "clipper2/clipper.core.h"
#include "clipper2/clipper.engine.h"
#include "clipper2/clipper.offset.h"
#include "clipper2/clipper.rectclip.h"
namespace Clipper2Lib {
typedef int64_t* CPath64;
typedef int64_t** CPaths64;
typedef double* CPathD;
typedef double** CPathsD;
typedef struct CPolyPath64 {
CPath64 polygon;
uint32_t is_hole;
uint32_t child_count;
CPolyPath64* childs;
}
CPolyTree64;
typedef struct CPolyPathD {
CPathD polygon;
uint32_t is_hole;
uint32_t child_count;
CPolyPathD* childs;
}
CPolyTreeD;
template <typename T>
struct CRect {
T left;
T top;
T right;
T bottom;
};
typedef CRect<int64_t> CRect64;
typedef CRect<double> CRectD;
template <typename T>
inline bool CRectIsEmpty(const CRect<T>& rect)
{
return (rect.right <= rect.left) || (rect.bottom <= rect.top);
}
template <typename T>
inline Rect<T> CRectToRect(const CRect<T>& rect)
{
Rect<T> result;
result.left = rect.left;
result.top = rect.top;
result.right = rect.right;
result.bottom = rect.bottom;
return result;
}
#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
//////////////////////////////////////////////////////
// EXPORTED FUNCTION DEFINITIONS
//////////////////////////////////////////////////////
EXTERN_DLL_EXPORT const char* Version();
// Some of the functions below will return data in the various CPath
// and CPolyTree structures which are pointers to heap allocated
// memory. Eventually this memory will need to be released with one
// of the following 'DisposeExported' functions. (This may be the
// only safe way to release this memory since the executable
// accessing these exported functions may use a memory manager that
// allocates and releases heap memory in a different way. Also,
// CPath structures that have been constructed by the executable
// should not be destroyed using these 'DisposeExported' functions.)
EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p);
EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp);
EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p);
EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp);
EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt);
EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt);
// Boolean clipping:
// cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4
// fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3
EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
uint8_t fillrule, const CPaths64 subjects,
const CPaths64 subjects_open, const CPaths64 clips,
CPaths64& solution, CPaths64& solution_open,
bool preserve_collinear = true, bool reverse_solution = false);
EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype,
uint8_t fillrule, const CPaths64 subjects,
const CPaths64 subjects_open, const CPaths64 clips,
CPolyTree64*& solution, CPaths64& solution_open,
bool preserve_collinear = true, bool reverse_solution = false);
EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype,
uint8_t fillrule, const CPathsD subjects,
const CPathsD subjects_open, const CPathsD clips,
CPathsD& solution, CPathsD& solution_open, int precision = 2,
bool preserve_collinear = true, bool reverse_solution = false);
EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype,
uint8_t fillrule, const CPathsD subjects,
const CPathsD subjects_open, const CPathsD clips,
CPolyTreeD*& solution, CPathsD& solution_open, int precision = 2,
bool preserve_collinear = true, bool reverse_solution = false);
// Polygon offsetting (inflate/deflate):
// jointype: Square=0, Round=1, Miter=2
// endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4
EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
double delta, uint8_t jointype, uint8_t endtype,
double miter_limit = 2.0, double arc_tolerance = 0.0,
bool reverse_solution = false);
EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
double delta, uint8_t jointype, uint8_t endtype,
int precision = 2, double miter_limit = 2.0,
double arc_tolerance = 0.0, bool reverse_solution = false);
// RectClip & RectClipLines:
EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
const CPaths64 paths);
EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
const CPathsD paths, int precision = 2);
EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
const CPaths64 paths);
EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
const CPathsD paths, int precision = 2);
//////////////////////////////////////////////////////
// INTERNAL FUNCTIONS
//////////////////////////////////////////////////////
inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2);
inline CPath64 CreateCPath64(const Path64& p);
inline CPaths64 CreateCPaths64(const Paths64& pp);
inline Path64 ConvertCPath64(const CPath64& p);
inline Paths64 ConvertCPaths64(const CPaths64& pp);
inline CPathD CreateCPathD(size_t cnt1, size_t cnt2);
inline CPathD CreateCPathD(const PathD& p);
inline CPathsD CreateCPathsD(const PathsD& pp);
inline PathD ConvertCPathD(const CPathD& p);
inline PathsD ConvertCPathsD(const CPathsD& pp);
// the following function avoid multiple conversions
inline CPathD CreateCPathD(const Path64& p, double scale);
inline CPathsD CreateCPathsD(const Paths64& pp, double scale);
inline Path64 ConvertCPathD(const CPathD& p, double scale);
inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale);
inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt);
inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale);
EXTERN_DLL_EXPORT const char* Version()
{
return CLIPPER2_VERSION;
}
EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p)
{
if (p) delete[] p;
}
EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp)
{
if (!pp) return;
CPaths64 v = pp;
CPath64 cnts = *v;
const size_t cnt = static_cast<size_t>(cnts[1]);
for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1
DisposeExportedCPath64(*v++);
delete[] pp;
pp = nullptr;
}
EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p)
{
if (p) delete[] p;
}
EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp)
{
if (!pp) return;
CPathsD v = pp;
CPathD cnts = *v;
size_t cnt = static_cast<size_t>(cnts[1]);
for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1
DisposeExportedCPathD(*v++);
delete[] pp;
pp = nullptr;
}
EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
uint8_t fillrule, const CPaths64 subjects,
const CPaths64 subjects_open, const CPaths64 clips,
CPaths64& solution, CPaths64& solution_open,
bool preserve_collinear, bool reverse_solution)
{
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
Paths64 sub, sub_open, clp, sol, sol_open;
sub = ConvertCPaths64(subjects);
sub_open = ConvertCPaths64(subjects_open);
clp = ConvertCPaths64(clips);
Clipper64 clipper;
clipper.PreserveCollinear = preserve_collinear;
clipper.ReverseSolution = reverse_solution;
if (sub.size() > 0) clipper.AddSubject(sub);
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
if (clp.size() > 0) clipper.AddClip(clp);
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open))
return -1; // clipping bug - should never happen :)
solution = CreateCPaths64(sol);
solution_open = CreateCPaths64(sol_open);
return 0; //success !!
}
EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype,
uint8_t fillrule, const CPaths64 subjects,
const CPaths64 subjects_open, const CPaths64 clips,
CPolyTree64*& solution, CPaths64& solution_open,
bool preserve_collinear, bool reverse_solution)
{
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
Paths64 sub, sub_open, clp, sol_open;
sub = ConvertCPaths64(subjects);
sub_open = ConvertCPaths64(subjects_open);
clp = ConvertCPaths64(clips);
PolyTree64 pt;
Clipper64 clipper;
clipper.PreserveCollinear = preserve_collinear;
clipper.ReverseSolution = reverse_solution;
if (sub.size() > 0) clipper.AddSubject(sub);
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
if (clp.size() > 0) clipper.AddClip(clp);
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), pt, sol_open))
return -1; // clipping bug - should never happen :)
solution = CreateCPolyTree64(pt);
solution_open = CreateCPaths64(sol_open);
return 0; //success !!
}
EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype,
uint8_t fillrule, const CPathsD subjects,
const CPathsD subjects_open, const CPathsD clips,
CPathsD& solution, CPathsD& solution_open, int precision,
bool preserve_collinear, bool reverse_solution)
{
if (precision < -8 || precision > 8) return -5;
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
const double scale = std::pow(10, precision);
Paths64 sub, sub_open, clp, sol, sol_open;
sub = ConvertCPathsD(subjects, scale);
sub_open = ConvertCPathsD(subjects_open, scale);
clp = ConvertCPathsD(clips, scale);
Clipper64 clipper;
clipper.PreserveCollinear = preserve_collinear;
clipper.ReverseSolution = reverse_solution;
if (sub.size() > 0) clipper.AddSubject(sub);
if (sub_open.size() > 0)
clipper.AddOpenSubject(sub_open);
if (clp.size() > 0) clipper.AddClip(clp);
if (!clipper.Execute(ClipType(cliptype),
FillRule(fillrule), sol, sol_open)) return -1;
if (sol.size() > 0) solution = CreateCPathsD(sol, 1 / scale);
if (sol_open.size() > 0)
solution_open = CreateCPathsD(sol_open, 1 / scale);
return 0;
}
EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype,
uint8_t fillrule, const CPathsD subjects,
const CPathsD subjects_open, const CPathsD clips,
CPolyTreeD*& solution, CPathsD& solution_open, int precision,
bool preserve_collinear, bool reverse_solution)
{
if (precision < -8 || precision > 8) return -5;
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
const double scale = std::pow(10, precision);
Paths64 sub, sub_open, clp, sol_open;
sub = ConvertCPathsD(subjects, scale);
sub_open = ConvertCPathsD(subjects_open, scale);
clp = ConvertCPathsD(clips, scale);
PolyTree64 sol;
Clipper64 clipper;
clipper.PreserveCollinear = preserve_collinear;
clipper.ReverseSolution = reverse_solution;
if (sub.size() > 0) clipper.AddSubject(sub);
if (sub_open.size() > 0)
clipper.AddOpenSubject(sub_open);
if (clp.size() > 0) clipper.AddClip(clp);
if (!clipper.Execute(ClipType(cliptype),
FillRule(fillrule), sol, sol_open)) return -1;
solution = CreateCPolyTreeD(sol, 1 / scale);
if (sol_open.size() > 0)
solution_open = CreateCPathsD(sol_open, 1 / scale);
return 0;
}
EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
double delta, uint8_t jointype, uint8_t endtype, double miter_limit,
double arc_tolerance, bool reverse_solution)
{
Paths64 pp;
pp = ConvertCPaths64(paths);
ClipperOffset clip_offset( miter_limit,
arc_tolerance, reverse_solution);
clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
Paths64 result = clip_offset.Execute(delta);
return CreateCPaths64(result);
}
EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
double delta, uint8_t jointype, uint8_t endtype,
int precision, double miter_limit,
double arc_tolerance, bool reverse_solution)
{
if (precision < -8 || precision > 8 || !paths) return nullptr;
const double scale = std::pow(10, precision);
ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution);
Paths64 pp = ConvertCPathsD(paths, scale);
clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
Paths64 result = clip_offset.Execute(delta * scale);
return CreateCPathsD(result, 1/scale);
}
EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
const CPaths64 paths)
{
if (CRectIsEmpty(rect) || !paths) return nullptr;
Rect64 r64 = CRectToRect(rect);
class RectClip rc(r64);
Paths64 pp = ConvertCPaths64(paths);
Paths64 result;
result.reserve(pp.size());
for (const Path64& p : pp)
{
Rect64 pathRec = Bounds(p);
if (!r64.Intersects(pathRec)) continue;
if (r64.Contains(pathRec))
result.push_back(p);
else
{
Path64 p2 = rc.Execute(p);
if (!p2.empty()) result.push_back(std::move(p2));
}
}
return CreateCPaths64(result);
}
EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
const CPathsD paths, int precision)
{
if (CRectIsEmpty(rect) || !paths) return nullptr;
if (precision < -8 || precision > 8) return nullptr;
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale);
Paths64 pp = ConvertCPathsD(paths, scale);
class RectClip rc(r);
Paths64 result;
result.reserve(pp.size());
for (const Path64& p : pp)
{
Rect64 pathRec = Bounds(p);
if (!r.Intersects(pathRec)) continue;
if (r.Contains(pathRec))
result.push_back(p);
else
{
Path64 p2 = rc.Execute(p);
if (!p2.empty()) result.push_back(std::move(p2));
}
}
return CreateCPathsD(result, 1/scale);
}
EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
const CPaths64 paths)
{
if (CRectIsEmpty(rect) || !paths) return nullptr;
Rect64 r = CRectToRect(rect);
class RectClipLines rcl (r);
Paths64 pp = ConvertCPaths64(paths);
Paths64 result;
result.reserve(pp.size());
for (const Path64& p : pp)
{
Rect64 pathRec = Bounds(p);
if (!r.Intersects(pathRec)) continue;
if (r.Contains(pathRec))
result.push_back(p);
else
{
Paths64 pp2 = rcl.Execute(p);
if (!pp2.empty())
result.insert(result.end(), pp2.begin(), pp2.end());
}
}
return CreateCPaths64(result);
}
EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
const CPathsD paths, int precision)
{
Paths64 result;
if (CRectIsEmpty(rect) || !paths) return nullptr;
if (precision < -8 || precision > 8) return nullptr;
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale);
class RectClipLines rcl(r);
Paths64 pp = ConvertCPathsD(paths, scale);
result.reserve(pp.size());
for (const Path64& p : pp)
{
Rect64 pathRec = Bounds(p);
if (!r.Intersects(pathRec)) continue;
if (r.Contains(pathRec))
result.push_back(p);
else
{
Paths64 pp2 = rcl.Execute(p);
if (pp2.empty()) continue;
result.insert(result.end(), pp2.begin(), pp2.end());
}
}
return CreateCPathsD(result, 1/scale);
}
inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2)
{
// allocates memory for CPath64, fills in the counter, and
// returns the structure ready to be filled with path data
CPath64 result = new int64_t[2 + cnt1 *2];
result[0] = cnt1;
result[1] = cnt2;
return result;
}
inline CPath64 CreateCPath64(const Path64& p)
{
// allocates memory for CPath64, fills the counter
// and returns the memory filled with path data
size_t cnt = p.size();
if (!cnt) return nullptr;
CPath64 result = CreateCPath64(cnt, 0);
CPath64 v = result;
v += 2; // skip counters
for (const Point64& pt : p)
{
*v++ = pt.x;
*v++ = pt.y;
}
return result;
}
inline Path64 ConvertCPath64(const CPath64& p)
{
Path64 result;
if (p && *p)
{
CPath64 v = p;
const size_t cnt = static_cast<size_t>(p[0]);
v += 2; // skip counters
result.reserve(cnt);
for (size_t i = 0; i < cnt; ++i)
{
// x,y here avoids right to left function evaluation
// result.push_back(Point64(*v++, *v++));
int64_t x = *v++;
int64_t y = *v++;
result.push_back(Point64(x, y));
}
}
return result;
}
inline CPaths64 CreateCPaths64(const Paths64& pp)
{
// allocates memory for multiple CPath64 and
// and returns this memory filled with path data
size_t cnt = pp.size(), cnt2 = cnt;
// don't allocate space for empty paths
for (size_t i = 0; i < cnt; ++i)
if (!pp[i].size()) --cnt2;
if (!cnt2) return nullptr;
CPaths64 result = new int64_t* [cnt2 + 1];
CPaths64 v = result;
*v++ = CreateCPath64(0, cnt2); // assign a counter path
for (const Path64& p : pp)
{
*v = CreateCPath64(p);
if (*v) ++v;
}
return result;
}
inline Paths64 ConvertCPaths64(const CPaths64& pp)
{
Paths64 result;
if (pp)
{
CPaths64 v = pp;
CPath64 cnts = pp[0];
const size_t cnt = static_cast<size_t>(cnts[1]); // nb 2nd cnt
++v; // skip cnts
result.reserve(cnt);
for (size_t i = 0; i < cnt; ++i)
result.push_back(ConvertCPath64(*v++));
}
return result;
}
inline CPathD CreateCPathD(size_t cnt1, size_t cnt2)
{
// allocates memory for CPathD, fills in the counter, and
// returns the structure ready to be filled with path data
CPathD result = new double[2 + cnt1 * 2];
result[0] = static_cast<double>(cnt1);
result[1] = static_cast<double>(cnt2);
return result;
}
inline CPathD CreateCPathD(const PathD& p)
{
// allocates memory for CPath, fills the counter
// and returns the memory fills with path data
size_t cnt = p.size();
if (!cnt) return nullptr;
CPathD result = CreateCPathD(cnt, 0);
CPathD v = result;
v += 2; // skip counters
for (const PointD& pt : p)
{
*v++ = pt.x;
*v++ = pt.y;
}
return result;
}
inline PathD ConvertCPathD(const CPathD& p)
{
PathD result;
if (p)
{
CPathD v = p;
size_t cnt = static_cast<size_t>(v[0]);
v += 2; // skip counters
result.reserve(cnt);
for (size_t i = 0; i < cnt; ++i)
{
// x,y here avoids right to left function evaluation
// result.push_back(PointD(*v++, *v++));
double x = *v++;
double y = *v++;
result.push_back(PointD(x, y));
}
}
return result;
}
inline CPathsD CreateCPathsD(const PathsD& pp)
{
size_t cnt = pp.size(), cnt2 = cnt;
// don't allocate space for empty paths
for (size_t i = 0; i < cnt; ++i)
if (!pp[i].size()) --cnt2;
if (!cnt2) return nullptr;
CPathsD result = new double * [cnt2 + 1];
CPathsD v = result;
*v++ = CreateCPathD(0, cnt2); // assign counter path
for (const PathD& p : pp)
{
*v = CreateCPathD(p);
if (*v) { ++v; }
}
return result;
}
inline PathsD ConvertCPathsD(const CPathsD& pp)
{
PathsD result;
if (pp)
{
CPathsD v = pp;
CPathD cnts = v[0];
size_t cnt = static_cast<size_t>(cnts[1]);
++v; // skip cnts path
result.reserve(cnt);
for (size_t i = 0; i < cnt; ++i)
result.push_back(ConvertCPathD(*v++));
}
return result;
}
inline Path64 ConvertCPathD(const CPathD& p, double scale)
{
Path64 result;
if (p)
{
CPathD v = p;
size_t cnt = static_cast<size_t>(*v);
v += 2; // skip counters
result.reserve(cnt);
for (size_t i = 0; i < cnt; ++i)
{
// x,y here avoids right to left function evaluation
// result.push_back(PointD(*v++, *v++));
double x = *v++ * scale;
double y = *v++ * scale;
result.push_back(Point64(x, y));
}
}
return result;
}
inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale)
{
Paths64 result;
if (pp)
{
CPathsD v = pp;
CPathD cnts = v[0];
size_t cnt = static_cast<size_t>(cnts[1]);
result.reserve(cnt);
++v; // skip cnts path
for (size_t i = 0; i < cnt; ++i)
result.push_back(ConvertCPathD(*v++, scale));
}
return result;
}
inline CPathD CreateCPathD(const Path64& p, double scale)
{
// allocates memory for CPathD, fills in the counter, and
// returns the structure filled with *scaled* path data
size_t cnt = p.size();
if (!cnt) return nullptr;
CPathD result = CreateCPathD(cnt, 0);
CPathD v = result;
v += 2; // skip cnts
for (const Point64& pt : p)
{
*v++ = pt.x * scale;
*v++ = pt.y * scale;
}
return result;
}
inline CPathsD CreateCPathsD(const Paths64& pp, double scale)
{
// allocates memory for *multiple* CPathD, and
// returns the structure filled with scaled path data
size_t cnt = pp.size(), cnt2 = cnt;
// don't allocate space for empty paths
for (size_t i = 0; i < cnt; ++i)
if (!pp[i].size()) --cnt2;
if (!cnt2) return nullptr;
CPathsD result = new double* [cnt2 + 1];
CPathsD v = result;
*v++ = CreateCPathD(0, cnt2);
for (const Path64& p : pp)
{
*v = CreateCPathD(p, scale);
if (*v) ++v;
}
return result;
}
inline void InitCPolyPath64(CPolyTree64* cpt,
bool is_hole, const std::unique_ptr <PolyPath64>& pp)
{
cpt->polygon = CreateCPath64(pp->Polygon());
cpt->is_hole = is_hole;
size_t child_cnt = pp->Count();
cpt->child_count = static_cast<uint32_t>(child_cnt);
cpt->childs = nullptr;
if (!child_cnt) return;
cpt->childs = new CPolyPath64[child_cnt];
CPolyPath64* child = cpt->childs;
for (const std::unique_ptr <PolyPath64>& pp_child : *pp)
InitCPolyPath64(child++, !is_hole, pp_child);
}
inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt)
{
CPolyTree64* result = new CPolyTree64();
result->polygon = nullptr;
result->is_hole = false;
size_t child_cnt = pt.Count();
result->childs = nullptr;
result->child_count = static_cast<uint32_t>(child_cnt);
if (!child_cnt) return result;
result->childs = new CPolyPath64[child_cnt];
CPolyPath64* child = result->childs;
for (const std::unique_ptr <PolyPath64>& pp : pt)
InitCPolyPath64(child++, true, pp);
return result;
}
inline void DisposeCPolyPath64(CPolyPath64* cpp)
{
if (!cpp->child_count) return;
CPolyPath64* child = cpp->childs;
for (size_t i = 0; i < cpp->child_count; ++i)
DisposeCPolyPath64(child);
delete[] cpp->childs;
}
EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt)
{
if (!cpt) return;
DisposeCPolyPath64(cpt);
delete cpt;
cpt = nullptr;
}
inline void InitCPolyPathD(CPolyTreeD* cpt,
bool is_hole, const std::unique_ptr <PolyPath64>& pp, double scale)
{
cpt->polygon = CreateCPathD(pp->Polygon(), scale);
cpt->is_hole = is_hole;
size_t child_cnt = pp->Count();
cpt->child_count = static_cast<uint32_t>(child_cnt);
cpt->childs = nullptr;
if (!child_cnt) return;
cpt->childs = new CPolyPathD[child_cnt];
CPolyPathD* child = cpt->childs;
for (const std::unique_ptr <PolyPath64>& pp_child : *pp)
InitCPolyPathD(child++, !is_hole, pp_child, scale);
}
inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale)
{
CPolyTreeD* result = new CPolyTreeD();
result->polygon = nullptr;
result->is_hole = false;
size_t child_cnt = pt.Count();
result->child_count = static_cast<uint32_t>(child_cnt);
result->childs = nullptr;
if (!child_cnt) return result;
result->childs = new CPolyPathD[child_cnt];
CPolyPathD* child = result->childs;
for (const std::unique_ptr <PolyPath64>& pp : pt)
InitCPolyPathD(child++, true, pp, scale);
return result;
}
inline void DisposeCPolyPathD(CPolyPathD* cpp)
{
if (!cpp->child_count) return;
CPolyPathD* child = cpp->childs;
for (size_t i = 0; i < cpp->child_count; ++i)
DisposeCPolyPathD(child++);
delete[] cpp->childs;
}
EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt)
{
if (!cpt) return;
DisposeCPolyPathD(cpt);
delete cpt;
cpt = nullptr;
}
} // end Clipper2Lib namespace
#endif // CLIPPER2_EXPORT_H

View File

@ -0,0 +1,911 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 28 January 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : This module provides a simple interface to the Clipper Library *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_H
#define CLIPPER_H
#include <cstdlib>
#include <vector>
#include "clipper.core.h"
#include "clipper.engine.h"
#include "clipper.offset.h"
#include "clipper.minkowski.h"
#include "clipper.rectclip.h"
namespace Clipper2Lib {
inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
const Paths64& subjects, const Paths64& clips)
{
Paths64 result;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(cliptype, fillrule, result);
return result;
}
inline void BooleanOp(ClipType cliptype, FillRule fillrule,
const Paths64& subjects, const Paths64& clips, PolyTree64& solution)
{
Paths64 sol_open;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(cliptype, fillrule, solution, sol_open);
}
inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule,
const PathsD& subjects, const PathsD& clips, int precision = 2)
{
int error_code = 0;
CheckPrecision(precision, error_code);
PathsD result;
if (error_code) return result;
ClipperD clipper(precision);
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(cliptype, fillrule, result);
return result;
}
inline void BooleanOp(ClipType cliptype, FillRule fillrule,
const PathsD& subjects, const PathsD& clips,
PolyTreeD& polytree, int precision = 2)
{
polytree.Clear();
int error_code = 0;
CheckPrecision(precision, error_code);
if (error_code) return;
ClipperD clipper(precision);
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(cliptype, fillrule, polytree);
}
inline Paths64 Intersect(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips);
}
inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec);
}
inline Paths64 Union(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Union, fillrule, subjects, clips);
}
inline PathsD Union(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Union, fillrule, subjects, clips, decimal_prec);
}
inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
{
Paths64 result;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.Execute(ClipType::Union, fillrule, result);
return result;
}
inline PathsD Union(const PathsD& subjects, FillRule fillrule, int precision = 2)
{
PathsD result;
int error_code = 0;
CheckPrecision(precision, error_code);
if (error_code) return result;
ClipperD clipper(precision);
clipper.AddSubject(subjects);
clipper.Execute(ClipType::Union, fillrule, result);
return result;
}
inline Paths64 Difference(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Difference, fillrule, subjects, clips);
}
inline PathsD Difference(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Difference, fillrule, subjects, clips, decimal_prec);
}
inline Paths64 Xor(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
{
return BooleanOp(ClipType::Xor, fillrule, subjects, clips);
}
inline PathsD Xor(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Xor, fillrule, subjects, clips, decimal_prec);
}
inline Paths64 InflatePaths(const Paths64& paths, double delta,
JoinType jt, EndType et, double miter_limit = 2.0)
{
ClipperOffset clip_offset(miter_limit);
clip_offset.AddPaths(paths, jt, et);
return clip_offset.Execute(delta);
}
inline PathsD InflatePaths(const PathsD& paths, double delta,
JoinType jt, EndType et, double miter_limit = 2.0, int precision = 2)
{
int error_code = 0;
CheckPrecision(precision, error_code);
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
ClipperOffset clip_offset(miter_limit);
clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et);
if (error_code) return PathsD();
Paths64 tmp = clip_offset.Execute(delta * scale);
return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
}
inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
{
Path64 result;
result.reserve(path.size());
std::transform(path.begin(), path.end(), back_inserter(result),
[dx, dy](const auto& pt) { return Point64(pt.x + dx, pt.y +dy); });
return result;
}
inline PathD TranslatePath(const PathD& path, double dx, double dy)
{
PathD result;
result.reserve(path.size());
std::transform(path.begin(), path.end(), back_inserter(result),
[dx, dy](const auto& pt) { return PointD(pt.x + dx, pt.y + dy); });
return result;
}
inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
{
Paths64 result;
result.reserve(paths.size());
std::transform(paths.begin(), paths.end(), back_inserter(result),
[dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
return result;
}
inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
{
PathsD result;
result.reserve(paths.size());
std::transform(paths.begin(), paths.end(), back_inserter(result),
[dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
return result;
}
inline Path64 RectClip(const Rect64& rect, const Path64& path)
{
if (rect.IsEmpty() || path.empty()) return Path64();
Rect64 pathRec = Bounds(path);
if (!rect.Intersects(pathRec)) return Path64();
if (rect.Contains(pathRec)) return path;
class RectClip rc(rect);
return rc.Execute(path);
}
inline Paths64 RectClip(const Rect64& rect, const Paths64& paths)
{
if (rect.IsEmpty() || paths.empty()) return Paths64();
class RectClip rc(rect);
Paths64 result;
result.reserve(paths.size());
for (const Path64& p : paths)
{
Rect64 pathRec = Bounds(p);
if (!rect.Intersects(pathRec))
continue;
else if (rect.Contains(pathRec))
result.push_back(p);
else
{
Path64 p2 = rc.Execute(p);
if (!p2.empty()) result.push_back(std::move(p2));
}
}
return result;
}
inline PathD RectClip(const RectD& rect, const PathD& path, int precision = 2)
{
if (rect.IsEmpty() || path.empty() ||
!rect.Contains(Bounds(path))) return PathD();
int error_code = 0;
CheckPrecision(precision, error_code);
if (error_code) return PathD();
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
class RectClip rc(r);
Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
if (error_code) return PathD();
return ScalePath<double, int64_t>(rc.Execute(p), 1 / scale, error_code);
}
inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2)
{
if (rect.IsEmpty() || paths.empty()) return PathsD();
int error_code = 0;
CheckPrecision(precision, error_code);
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
class RectClip rc(r);
PathsD result;
result.reserve(paths.size());
for (const PathD& path : paths)
{
RectD pathRec = Bounds(path);
if (!rect.Intersects(pathRec))
continue;
else if (rect.Contains(pathRec))
result.push_back(path);
else
{
Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
if (error_code) return PathsD();
p = rc.Execute(p);
if (p.empty()) continue;
result.push_back(ScalePath<double, int64_t>(p, 1/scale, error_code));
if (error_code) return PathsD();
}
}
return result;
}
inline Paths64 RectClipLines(const Rect64& rect, const Path64& path)
{
Paths64 result;
if (rect.IsEmpty() || path.empty()) return result;
Rect64 pathRec = Bounds(path);
if (!rect.Intersects(pathRec)) return result;
if (rect.Contains(pathRec))
{
result.push_back(path);
return result;
}
class RectClipLines rcl(rect);
return rcl.Execute(path);
}
inline Paths64 RectClipLines(const Rect64& rect, const Paths64& paths)
{
Paths64 result;
if (rect.IsEmpty() || paths.empty()) return result;
class RectClipLines rcl(rect);
for (const Path64& p : paths)
{
Rect64 pathRec = Bounds(p);
if (!rect.Intersects(pathRec))
continue;
else if (rect.Contains(pathRec))
result.push_back(p);
else
{
Paths64 pp = rcl.Execute(p);
if (!pp.empty())
result.insert(result.end(), pp.begin(), pp.end());
}
}
return result;
}
inline PathsD RectClipLines(const RectD& rect, const PathD& path, int precision = 2)
{
if (rect.IsEmpty() || path.empty() ||
!rect.Contains(Bounds(path))) return PathsD();
int error_code = 0;
CheckPrecision(precision, error_code);
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
class RectClipLines rcl(r);
Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
if (error_code) return PathsD();
return ScalePaths<double, int64_t>(rcl.Execute(p), 1 / scale, error_code);
}
inline PathsD RectClipLines(const RectD& rect, const PathsD& paths, int precision = 2)
{
PathsD result;
if (rect.IsEmpty() || paths.empty()) return result;
int error_code = 0;
CheckPrecision(precision, error_code);
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
class RectClipLines rcl(r);
result.reserve(paths.size());
for (const PathD& path : paths)
{
RectD pathRec = Bounds(path);
if (!rect.Intersects(pathRec))
continue;
else if (rect.Contains(pathRec))
result.push_back(path);
else
{
Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
if (error_code) return PathsD();
Paths64 pp = rcl.Execute(p);
if (pp.empty()) continue;
PathsD ppd = ScalePaths<double, int64_t>(pp, 1 / scale, error_code);
if (error_code) return PathsD();
result.insert(result.end(), ppd.begin(), ppd.end());
}
}
return result;
}
namespace details
{
inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths)
{
paths.push_back(polypath.Polygon());
for (const auto& child : polypath)
PolyPathToPaths64(*child, paths);
}
inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths)
{
paths.push_back(polypath.Polygon());
for (const auto& child : polypath)
PolyPathToPathsD(*child, paths);
}
inline bool PolyPath64ContainsChildren(const PolyPath64& pp)
{
for (const auto& child : pp)
{
// return false if this child isn't fully contained by its parent
// the following algorithm is a bit too crude, and doesn't account
// for rounding errors. A better algorithm is to return false when
// consecutive vertices are found outside the parent's polygon.
//const Path64& path = pp.Polygon();
//if (std::any_of(child->Polygon().cbegin(), child->Polygon().cend(),
// [path](const auto& pt) {return (PointInPolygon(pt, path) ==
// PointInPolygonResult::IsOutside); })) return false;
int outsideCnt = 0;
for (const Point64& pt : child->Polygon())
{
PointInPolygonResult result = PointInPolygon(pt, pp.Polygon());
if (result == PointInPolygonResult::IsInside) --outsideCnt;
else if (result == PointInPolygonResult::IsOutside) ++outsideCnt;
if (outsideCnt > 1) return false;
else if (outsideCnt < -1) break;
}
// now check any nested children too
if (child->Count() > 0 && !PolyPath64ContainsChildren(*child))
return false;
}
return true;
}
inline bool GetInt(std::string::const_iterator& iter, const
std::string::const_iterator& end_iter, int64_t& val)
{
val = 0;
bool is_neg = *iter == '-';
if (is_neg) ++iter;
std::string::const_iterator start_iter = iter;
while (iter != end_iter &&
((*iter >= '0') && (*iter <= '9')))
{
val = val * 10 + (static_cast<int64_t>(*iter++) - '0');
}
if (is_neg) val = -val;
return (iter != start_iter);
}
inline bool GetFloat(std::string::const_iterator& iter, const
std::string::const_iterator& end_iter, double& val)
{
val = 0;
bool is_neg = *iter == '-';
if (is_neg) ++iter;
int dec_pos = 1;
const std::string::const_iterator start_iter = iter;
while (iter != end_iter && (*iter == '.' ||
((*iter >= '0') && (*iter <= '9'))))
{
if (*iter == '.')
{
if (dec_pos != 1) break;
dec_pos = 0;
++iter;
continue;
}
if (dec_pos != 1) --dec_pos;
val = val * 10 + ((int64_t)(*iter++) - '0');
}
if (iter == start_iter || dec_pos == 0) return false;
if (dec_pos < 0)
val *= std::pow(10, dec_pos);
if (is_neg)
val *= -1;
return true;
}
inline void SkipWhiteSpace(std::string::const_iterator& iter,
const std::string::const_iterator& end_iter)
{
while (iter != end_iter && *iter <= ' ') ++iter;
}
inline void SkipSpacesWithOptionalComma(std::string::const_iterator& iter,
const std::string::const_iterator& end_iter)
{
bool comma_seen = false;
while (iter != end_iter)
{
if (*iter == ' ') ++iter;
else if (*iter == ',')
{
if (comma_seen) return; // don't skip 2 commas!
comma_seen = true;
++iter;
}
else return;
}
}
inline bool has_one_match(const char c, char* chrs)
{
while (*chrs > 0 && c != *chrs) ++chrs;
if (!*chrs) return false;
*chrs = ' '; // only match once per char
return true;
}
inline void SkipUserDefinedChars(std::string::const_iterator& iter,
const std::string::const_iterator& end_iter, const std::string& skip_chars)
{
const size_t MAX_CHARS = 16;
char buff[MAX_CHARS] = {0};
std::copy(skip_chars.cbegin(), skip_chars.cend(), &buff[0]);
while (iter != end_iter &&
(*iter <= ' ' || has_one_match(*iter, buff))) ++iter;
return;
}
static void OutlinePolyPath(std::ostream& os,
bool isHole, size_t count, const std::string& preamble)
{
std::string plural = (count == 1) ? "." : "s.";
if (isHole)
{
if (count)
os << preamble << "+- Hole with " << count <<
" nested polygon" << plural << std::endl;
else
os << preamble << "+- Hole" << std::endl;
}
else
{
if (count)
os << preamble << "+- Polygon with " << count <<
" hole" << plural << std::endl;
else
os << preamble << "+- Polygon" << std::endl;
}
}
static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp,
std::string preamble, bool last_child)
{
OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble);
preamble += (!last_child) ? "| " : " ";
if (pp.Count())
{
PolyPath64List::const_iterator it = pp.begin();
for (; it < pp.end() - 1; ++it)
OutlinePolyPath64(os, **it, preamble, false);
OutlinePolyPath64(os, **it, preamble, true);
}
}
static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp,
std::string preamble, bool last_child)
{
OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble);
preamble += (!last_child) ? "| " : " ";
if (pp.Count())
{
PolyPathDList::const_iterator it = pp.begin();
for (; it < pp.end() - 1; ++it)
OutlinePolyPathD(os, **it, preamble, false);
OutlinePolyPathD(os, **it, preamble, true);
}
}
} // end details namespace
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
{
PolyPath64List::const_iterator it = pp.begin();
for (; it < pp.end() - 1; ++it)
details::OutlinePolyPath64(os, **it, " ", false);
details::OutlinePolyPath64(os, **it, " ", true);
os << std::endl << std::endl;
if (!pp.Level()) os << std::endl;
return os;
}
inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp)
{
PolyPathDList::const_iterator it = pp.begin();
for (; it < pp.end() - 1; ++it)
details::OutlinePolyPathD(os, **it, " ", false);
details::OutlinePolyPathD(os, **it, " ", true);
os << std::endl << std::endl;
if (!pp.Level()) os << std::endl;
return os;
}
inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree)
{
Paths64 result;
for (const auto& child : polytree)
details::PolyPathToPaths64(*child, result);
return result;
}
inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree)
{
PathsD result;
for (const auto& child : polytree)
details::PolyPathToPathsD(*child, result);
return result;
}
inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree)
{
for (const auto& child : polytree)
if (child->Count() > 0 &&
!details::PolyPath64ContainsChildren(*child))
return false;
return true;
}
inline Path64 MakePath(const std::string& s)
{
const std::string skip_chars = " ,(){}[]";
Path64 result;
std::string::const_iterator s_iter = s.cbegin();
details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
while (s_iter != s.cend())
{
int64_t y = 0, x = 0;
if (!details::GetInt(s_iter, s.cend(), x)) break;
details::SkipSpacesWithOptionalComma(s_iter, s.cend());
if (!details::GetInt(s_iter, s.cend(), y)) break;
result.push_back(Point64(x, y));
details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
}
return result;
}
inline PathD MakePathD(const std::string& s)
{
const std::string skip_chars = " ,(){}[]";
PathD result;
std::string::const_iterator s_iter = s.cbegin();
details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
while (s_iter != s.cend())
{
double y = 0, x = 0;
if (!details::GetFloat(s_iter, s.cend(), x)) break;
details::SkipSpacesWithOptionalComma(s_iter, s.cend());
if (!details::GetFloat(s_iter, s.cend(), y)) break;
result.push_back(PointD(x, y));
details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars);
}
return result;
}
inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false)
{
size_t len = p.size();
if (len < 3)
{
if (!is_open_path || len < 2 || p[0] == p[1]) return Path64();
else return p;
}
Path64 dst;
dst.reserve(len);
Path64::const_iterator srcIt = p.cbegin(), prevIt, stop = p.cend() - 1;
if (!is_open_path)
{
while (srcIt != stop && !CrossProduct(*stop, *srcIt, *(srcIt + 1)))
++srcIt;
while (srcIt != stop && !CrossProduct(*(stop - 1), *stop, *srcIt))
--stop;
if (srcIt == stop) return Path64();
}
prevIt = srcIt++;
dst.push_back(*prevIt);
for (; srcIt != stop; ++srcIt)
{
if (CrossProduct(*prevIt, *srcIt, *(srcIt + 1)))
{
prevIt = srcIt;
dst.push_back(*prevIt);
}
}
if (is_open_path)
dst.push_back(*srcIt);
else if (CrossProduct(*prevIt, *stop, dst[0]))
dst.push_back(*stop);
else
{
while (dst.size() > 2 &&
!CrossProduct(dst[dst.size() - 1], dst[dst.size() - 2], dst[0]))
dst.pop_back();
if (dst.size() < 3) return Path64();
}
return dst;
}
inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false)
{
int error_code = 0;
CheckPrecision(precision, error_code);
if (error_code) return PathD();
const double scale = std::pow(10, precision);
Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
if (error_code) return PathD();
p = TrimCollinear(p, is_open_path);
return ScalePath<double, int64_t>(p, 1/scale, error_code);
}
template <typename T>
inline double Distance(const Point<T> pt1, const Point<T> pt2)
{
return std::sqrt(DistanceSqr(pt1, pt2));
}
template <typename T>
inline double Length(const Path<T>& path, bool is_closed_path = false)
{
double result = 0.0;
if (path.size() < 2) return result;
auto it = path.cbegin(), stop = path.end() - 1;
for (; it != stop; ++it)
result += Distance(*it, *(it + 1));
if (is_closed_path)
result += Distance(*stop, *path.cbegin());
return result;
}
template <typename T>
inline bool NearCollinear(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3, double sin_sqrd_min_angle_rads)
{
double cp = std::abs(CrossProduct(pt1, pt2, pt3));
return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads;
}
template <typename T>
inline Path<T> Ellipse(const Rect<T>& rect, int steps = 0)
{
return Ellipse(rect.MidPoint(),
static_cast<double>(rect.Width()) *0.5,
static_cast<double>(rect.Height()) * 0.5, steps);
}
template <typename T>
inline Path<T> Ellipse(const Point<T>& center,
double radiusX, double radiusY = 0, int steps = 0)
{
if (radiusX <= 0) return Path<T>();
if (radiusY <= 0) radiusY = radiusX;
if (steps <= 2)
steps = static_cast<int>(PI * sqrt((radiusX + radiusY) / 2));
double si = std::sin(2 * PI / steps);
double co = std::cos(2 * PI / steps);
double dx = co, dy = si;
Path<T> result;
result.reserve(steps);
result.push_back(Point<T>(center.x + radiusX, static_cast<double>(center.y)));
for (int i = 1; i < steps; ++i)
{
result.push_back(Point<T>(center.x + radiusX * dx, center.y + radiusY * dy));
double x = dx * co - dy * si;
dy = dy * co + dx * si;
dx = x;
}
return result;
}
template <typename T>
inline double PerpendicDistFromLineSqrd(const Point<T>& pt,
const Point<T>& line1, const Point<T>& line2)
{
double a = static_cast<double>(pt.x - line1.x);
double b = static_cast<double>(pt.y - line1.y);
double c = static_cast<double>(line2.x - line1.x);
double d = static_cast<double>(line2.y - line1.y);
if (c == 0 && d == 0) return 0;
return Sqr(a * d - c * b) / (c * c + d * d);
}
inline size_t GetNext(size_t current, size_t high,
const std::vector<bool>& flags)
{
++current;
while (current <= high && flags[current]) ++current;
if (current <= high) return current;
current = 0;
while (flags[current]) ++current;
return current;
}
inline size_t GetPrior(size_t current, size_t high,
const std::vector<bool>& flags)
{
if (current == 0) current = high;
else --current;
while (current > 0 && flags[current]) --current;
if (!flags[current]) return current;
current = high;
while (flags[current]) --current;
return current;
}
template <typename T>
inline Path<T> SimplifyPath(const Path<T> path,
double epsilon, bool isOpenPath = false)
{
const size_t len = path.size(), high = len -1;
const double epsSqr = Sqr(epsilon);
if (len < 4) return Path<T>(path);
std::vector<bool> flags(len);
std::vector<double> distSqr(len);
size_t prior = high, curr = 0, start, next, prior2, next2;
if (isOpenPath)
{
distSqr[0] = MAX_DBL;
distSqr[high] = MAX_DBL;
}
else
{
distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
}
for (size_t i = 1; i < high; ++i)
distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
for (;;)
{
if (distSqr[curr] > epsSqr)
{
start = curr;
do
{
curr = GetNext(curr, high, flags);
} while (curr != start && distSqr[curr] > epsSqr);
if (curr == start) break;
}
prior = GetPrior(curr, high, flags);
next = GetNext(curr, high, flags);
if (next == prior) break;
if (distSqr[next] < distSqr[curr])
{
flags[next] = true;
next = GetNext(next, high, flags);
next2 = GetNext(next, high, flags);
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
if (next != high || !isOpenPath)
distSqr[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]);
curr = next;
}
else
{
flags[curr] = true;
curr = next;
next = GetNext(next, high, flags);
prior2 = GetPrior(prior, high, flags);
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
if (prior != 0 || !isOpenPath)
distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]);
}
}
Path<T> result;
result.reserve(len);
for (typename Path<T>::size_type i = 0; i < len; ++i)
if (!flags[i]) result.push_back(path[i]);
return result;
}
template <typename T>
inline Paths<T> SimplifyPaths(const Paths<T> paths,
double epsilon, bool isOpenPath = false)
{
Paths<T> result;
result.reserve(paths.size());
for (const auto& path : paths)
result.push_back(SimplifyPath(path, epsilon, isOpenPath));
return result;
}
template <typename T>
inline void RDP(const Path<T> path, std::size_t begin,
std::size_t end, double epsSqrd, std::vector<bool>& flags)
{
typename Path<T>::size_type idx = 0;
double max_d = 0;
while (end > begin && path[begin] == path[end]) flags[end--] = false;
for (typename Path<T>::size_type i = begin + 1; i < end; ++i)
{
// PerpendicDistFromLineSqrd - avoids expensive Sqrt()
double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]);
if (d <= max_d) continue;
max_d = d;
idx = i;
}
if (max_d <= epsSqrd) return;
flags[idx] = true;
if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags);
if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags);
}
template <typename T>
inline Path<T> RamerDouglasPeucker(const Path<T>& path, double epsilon)
{
const typename Path<T>::size_type len = path.size();
if (len < 5) return Path<T>(path);
std::vector<bool> flags(len);
flags[0] = true;
flags[len - 1] = true;
RDP(path, 0, len - 1, Sqr(epsilon), flags);
Path<T> result;
result.reserve(len);
for (typename Path<T>::size_type i = 0; i < len; ++i)
if (flags[i])
result.push_back(path[i]);
return result;
}
template <typename T>
inline Paths<T> RamerDouglasPeucker(const Paths<T>& paths, double epsilon)
{
Paths<T> result;
result.reserve(paths.size());
std::transform(paths.begin(), paths.end(), back_inserter(result),
[epsilon](const auto& path)
{ return RamerDouglasPeucker<T>(path, epsilon); });
return result;
}
} // end Clipper2Lib namespace
#endif // CLIPPER_H

View File

@ -0,0 +1,120 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 28 January 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : Minkowski Sum and Difference *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_MINKOWSKI_H
#define CLIPPER_MINKOWSKI_H
#include <cstdlib>
#include <vector>
#include <string>
#include "clipper.core.h"
namespace Clipper2Lib
{
namespace detail
{
inline Paths64 Minkowski(const Path64& pattern, const Path64& path, bool isSum, bool isClosed)
{
size_t delta = isClosed ? 0 : 1;
size_t patLen = pattern.size(), pathLen = path.size();
if (patLen == 0 || pathLen == 0) return Paths64();
Paths64 tmp;
tmp.reserve(pathLen);
if (isSum)
{
for (const Point64& p : path)
{
Path64 path2(pattern.size());
std::transform(pattern.cbegin(), pattern.cend(),
path2.begin(), [p](const Point64& pt2) {return p + pt2; });
tmp.push_back(path2);
}
}
else
{
for (const Point64& p : path)
{
Path64 path2(pattern.size());
std::transform(pattern.cbegin(), pattern.cend(),
path2.begin(), [p](const Point64& pt2) {return p - pt2; });
tmp.push_back(path2);
}
}
Paths64 result;
result.reserve((pathLen - delta) * patLen);
size_t g = isClosed ? pathLen - 1 : 0;
for (size_t h = patLen - 1, i = delta; i < pathLen; ++i)
{
for (size_t j = 0; j < patLen; j++)
{
Path64 quad;
quad.reserve(4);
{
quad.push_back(tmp[g][h]);
quad.push_back(tmp[i][h]);
quad.push_back(tmp[i][j]);
quad.push_back(tmp[g][j]);
};
if (!IsPositive(quad))
std::reverse(quad.begin(), quad.end());
result.push_back(quad);
h = j;
}
g = i;
}
return result;
}
inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
{
Paths64 result;
Clipper64 clipper;
clipper.AddSubject(subjects);
clipper.Execute(ClipType::Union, fillrule, result);
return result;
}
} // namespace internal
inline Paths64 MinkowskiSum(const Path64& pattern, const Path64& path, bool isClosed)
{
return detail::Union(detail::Minkowski(pattern, path, true, isClosed), FillRule::NonZero);
}
inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
{
int error_code = 0;
double scale = pow(10, decimalPlaces);
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero);
return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
}
inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed)
{
return detail::Union(detail::Minkowski(pattern, path, false, isClosed), FillRule::NonZero);
}
inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
{
int error_code = 0;
double scale = pow(10, decimalPlaces);
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero);
return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
}
} // Clipper2Lib namespace
#endif // CLIPPER_MINKOWSKI_H

View File

@ -0,0 +1,101 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 27 January 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : Path Offset (Inflate/Shrink) *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_OFFSET_H_
#define CLIPPER_OFFSET_H_
#include "clipper.core.h"
namespace Clipper2Lib {
enum class JoinType { Square, Round, Miter };
enum class EndType {Polygon, Joined, Butt, Square, Round};
//Butt : offsets both sides of a path, with square blunt ends
//Square : offsets both sides of a path, with square extended ends
//Round : offsets both sides of a path, with round extended ends
//Joined : offsets both sides of a path, with joined ends
//Polygon: offsets only one side of a closed path
class ClipperOffset {
private:
class Group {
public:
Paths64 paths_in_;
Paths64 paths_out_;
Path64 path_;
bool is_reversed_ = false;
JoinType join_type_;
EndType end_type_;
Group(const Paths64& paths, JoinType join_type, EndType end_type) :
paths_in_(paths), join_type_(join_type), end_type_(end_type) {}
};
int error_code_ = 0;
double group_delta_ = 0.0;
double abs_group_delta_ = 0.0;
double temp_lim_ = 0.0;
double steps_per_rad_ = 0.0;
PathD norms;
Paths64 solution;
std::vector<Group> groups_;
JoinType join_type_ = JoinType::Square;
double miter_limit_ = 0.0;
double arc_tolerance_ = 0.0;
bool preserve_collinear_ = false;
bool reverse_solution_ = false;
void DoSquare(Group& group, const Path64& path, size_t j, size_t k);
void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a);
void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle);
void BuildNormals(const Path64& path);
void OffsetPolygon(Group& group, Path64& path);
void OffsetOpenJoined(Group& group, Path64& path);
void OffsetOpenPath(Group& group, Path64& path, EndType endType);
void OffsetPoint(Group& group, Path64& path,
size_t j, size_t& k, bool reversing = false);
void DoGroupOffset(Group &group, double delta);
public:
explicit ClipperOffset(double miter_limit = 2.0,
double arc_tolerance = 0.0,
bool preserve_collinear = false,
bool reverse_solution = false) :
miter_limit_(miter_limit), arc_tolerance_(arc_tolerance),
preserve_collinear_(preserve_collinear),
reverse_solution_(reverse_solution) { };
~ClipperOffset() { Clear(); };
int ErrorCode() { return error_code_; };
void AddPath(const Path64& path, JoinType jt_, EndType et_);
void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
void AddPath(const PathD &p, JoinType jt_, EndType et_);
void AddPaths(const PathsD &p, JoinType jt_, EndType et_);
void Clear() { groups_.clear(); norms.clear(); };
Paths64 Execute(double delta);
double MiterLimit() const { return miter_limit_; }
void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; }
//ArcTolerance: needed for rounded offsets (See offset_triginometry2.svg)
double ArcTolerance() const { return arc_tolerance_; }
void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; }
bool PreserveCollinear() const { return preserve_collinear_; }
void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;}
bool ReverseSolution() const { return reverse_solution_; }
void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;}
};
}
#endif /* CLIPPER_OFFSET_H_ */

View File

@ -0,0 +1,50 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 26 October 2022 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 *
* Purpose : FAST rectangular clipping *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_RECTCLIP_H
#define CLIPPER_RECTCLIP_H
#include <cstdlib>
#include <vector>
#include "clipper.h"
#include "clipper.core.h"
namespace Clipper2Lib
{
enum class Location { Left, Top, Right, Bottom, Inside };
class RectClip {
protected:
const Rect64 rect_;
const Point64 mp_;
const Path64 rectPath_;
Path64 result_;
std::vector<Location> start_locs_;
void GetNextLocation(const Path64& path,
Location& loc, int& i, int highI);
void AddCorner(Location prev, Location curr);
void AddCorner(Location& loc, bool isClockwise);
public:
explicit RectClip(const Rect64& rect) :
rect_(rect),
mp_(rect.MidPoint()),
rectPath_(rect.AsPath()) {}
Path64 Execute(const Path64& path);
};
class RectClipLines : public RectClip {
public:
explicit RectClipLines(const Rect64& rect) : RectClip(rect) {};
Paths64 Execute(const Path64& path);
};
} // Clipper2Lib namespace
#endif // CLIPPER_RECTCLIP_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,494 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 27 January 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : Path Offset (Inflate/Shrink) *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#include <cmath>
#include "clipper2/clipper.h"
#include "clipper2/clipper.offset.h"
namespace Clipper2Lib {
const double default_arc_tolerance = 0.25;
const double floating_point_tolerance = 1e-12;
//------------------------------------------------------------------------------
// Miscellaneous methods
//------------------------------------------------------------------------------
void GetBoundsAndLowestPolyIdx(const Paths64& paths, Rect64& r, int & idx)
{
idx = -1;
r = MaxInvalidRect64;
int64_t lpx = 0;
for (int i = 0; i < static_cast<int>(paths.size()); ++i)
for (const Point64& p : paths[i])
{
if (p.y >= r.bottom)
{
if (p.y > r.bottom || p.x < lpx)
{
idx = i;
lpx = p.x;
r.bottom = p.y;
}
}
else if (p.y < r.top) r.top = p.y;
if (p.x > r.right) r.right = p.x;
else if (p.x < r.left) r.left = p.x;
}
if (idx < 0) r = Rect64(0, 0, 0, 0);
if (r.top == INT64_MIN) r.bottom = r.top;
if (r.left == INT64_MIN) r.left = r.right;
}
bool IsSafeOffset(const Rect64& r, int64_t delta)
{
if (delta < 0) return true;
return r.left > INT64_MIN + delta &&
r.right < INT64_MAX - delta &&
r.top > INT64_MIN + delta &&
r.bottom < INT64_MAX - delta;
}
PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
{
double dx, dy, inverse_hypot;
if (pt1 == pt2) return PointD(0.0, 0.0);
dx = static_cast<double>(pt2.x - pt1.x);
dy = static_cast<double>(pt2.y - pt1.y);
inverse_hypot = 1.0 / hypot(dx, dy);
dx *= inverse_hypot;
dy *= inverse_hypot;
return PointD(dy, -dx);
}
inline bool AlmostZero(double value, double epsilon = 0.001)
{
return std::fabs(value) < epsilon;
}
inline double Hypot(double x, double y)
{
//see https://stackoverflow.com/a/32436148/359538
return std::sqrt(x * x + y * y);
}
inline PointD NormalizeVector(const PointD& vec)
{
double h = Hypot(vec.x, vec.y);
if (AlmostZero(h)) return PointD(0,0);
double inverseHypot = 1 / h;
return PointD(vec.x * inverseHypot, vec.y * inverseHypot);
}
inline PointD GetAvgUnitVector(const PointD& vec1, const PointD& vec2)
{
return NormalizeVector(PointD(vec1.x + vec2.x, vec1.y + vec2.y));
}
inline bool IsClosedPath(EndType et)
{
return et == EndType::Polygon || et == EndType::Joined;
}
inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
{
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
}
inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
{
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
}
//------------------------------------------------------------------------------
// ClipperOffset methods
//------------------------------------------------------------------------------
void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_)
{
Paths64 paths;
paths.push_back(path);
AddPaths(paths, jt_, et_);
}
void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_)
{
if (paths.size() == 0) return;
groups_.push_back(Group(paths, jt_, et_));
}
void ClipperOffset::AddPath(const Clipper2Lib::PathD& path, JoinType jt_, EndType et_)
{
PathsD paths;
paths.push_back(path);
AddPaths(paths, jt_, et_);
}
void ClipperOffset::AddPaths(const PathsD& paths, JoinType jt_, EndType et_)
{
if (paths.size() == 0) return;
groups_.push_back(Group(PathsDToPaths64(paths), jt_, et_));
}
void ClipperOffset::BuildNormals(const Path64& path)
{
norms.clear();
norms.reserve(path.size());
if (path.size() == 0) return;
Path64::const_iterator path_iter, path_last_iter = --path.cend();
for (path_iter = path.cbegin(); path_iter != path_last_iter; ++path_iter)
norms.push_back(GetUnitNormal(*path_iter,*(path_iter +1)));
norms.push_back(GetUnitNormal(*path_last_iter, *(path.cbegin())));
}
inline PointD TranslatePoint(const PointD& pt, double dx, double dy)
{
return PointD(pt.x + dx, pt.y + dy);
}
inline PointD ReflectPoint(const PointD& pt, const PointD& pivot)
{
return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
}
PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b,
const PointD& pt2a, const PointD& pt2b)
{
if (pt1a.x == pt1b.x) //vertical
{
if (pt2a.x == pt2b.x) return PointD(0, 0);
double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
double b2 = pt2a.y - m2 * pt2a.x;
return PointD(pt1a.x, m2 * pt1a.x + b2);
}
else if (pt2a.x == pt2b.x) //vertical
{
double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
double b1 = pt1a.y - m1 * pt1a.x;
return PointD(pt2a.x, m1 * pt2a.x + b1);
}
else
{
double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
double b1 = pt1a.y - m1 * pt1a.x;
double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
double b2 = pt2a.y - m2 * pt2a.x;
if (m1 == m2) return PointD(0, 0);
double x = (b2 - b1) / (m1 - m2);
return PointD(x, m1 * x + b1);
}
}
void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k)
{
PointD vec;
if (j == k)
vec = PointD(norms[0].y, -norms[0].x);
else
vec = GetAvgUnitVector(
PointD(-norms[k].y, norms[k].x),
PointD(norms[j].y, -norms[j].x));
// now offset the original vertex delta units along unit vector
PointD ptQ = PointD(path[j]);
ptQ = TranslatePoint(ptQ, abs_group_delta_ * vec.x, abs_group_delta_ * vec.y);
// get perpendicular vertices
PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x);
PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x);
// get 2 vertices along one edge offset
PointD pt3 = GetPerpendicD(path[k], norms[k], group_delta_);
if (j == k)
{
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
//get the second intersect point through reflecion
group.path_.push_back(Point64(ReflectPoint(pt, ptQ)));
group.path_.push_back(Point64(pt));
}
else
{
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
group.path_.push_back(Point64(pt));
//get the second intersect point through reflecion
group.path_.push_back(Point64(ReflectPoint(pt, ptQ)));
}
}
void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a)
{
double q = group_delta_ / (cos_a + 1);
group.path_.push_back(Point64(
path[j].x + (norms[k].x + norms[j].x) * q,
path[j].y + (norms[k].y + norms[j].y) * q));
}
void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle)
{
//even though angle may be negative this is a convex join
Point64 pt = path[j];
int steps = static_cast<int>(std::floor(steps_per_rad_ * std::abs(angle)));
double step_sin = std::sin(angle / steps);
double step_cos = std::cos(angle / steps);
PointD pt2 = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
if (j == k) pt2.Negate();
group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y));
for (int i = 0; i < steps; ++i)
{
pt2 = PointD(pt2.x * step_cos - step_sin * pt2.y,
pt2.x * step_sin + pt2.y * step_cos);
group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y));
}
group.path_.push_back(GetPerpendic(path[j], norms[j], group_delta_));
}
void ClipperOffset::OffsetPoint(Group& group,
Path64& path, size_t j, size_t& k, bool reversing)
{
// Let A = change in angle where edges join
// A == 0: ie no change in angle (flat join)
// A == PI: edges 'spike'
// sin(A) < 0: right turning
// cos(A) < 0: change in angle is more than 90 degree
if (path[j] == path[k]) { k = j; return; }
double sin_a = CrossProduct(norms[j], norms[k]);
double cos_a = DotProduct(norms[j], norms[k]);
if (sin_a > 1.0) sin_a = 1.0;
else if (sin_a < -1.0) sin_a = -1.0;
bool almostNoAngle = AlmostZero(cos_a - 1);
bool is180DegSpike = AlmostZero(cos_a + 1) && reversing;
// when there's almost no angle of deviation or it's concave
if (almostNoAngle || is180DegSpike || (sin_a * group_delta_ < 0))
{
//almost no angle or concave
group.path_.push_back(GetPerpendic(path[j], norms[k], group_delta_));
// create a simple self-intersection that will be cleaned up later
if (!almostNoAngle) group.path_.push_back(path[j]);
group.path_.push_back(GetPerpendic(path[j], norms[j], group_delta_));
}
else
{
// it's convex
if (join_type_ == JoinType::Round)
DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
else if (join_type_ == JoinType::Miter)
{
// miter unless the angle is so acute the miter would exceeds ML
if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
else DoSquare(group, path, j, k);
}
// don't bother squaring angles that deviate < ~20 degrees because
// squaring will be indistinguishable from mitering and just be a lot slower
else if (cos_a > 0.9)
DoMiter(group, path, j, k, cos_a);
else
DoSquare(group, path, j, k);
}
k = j;
}
void ClipperOffset::OffsetPolygon(Group& group, Path64& path)
{
group.path_.clear();
for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i)
OffsetPoint(group, path, i, j);
group.paths_out_.push_back(group.path_);
}
void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path)
{
OffsetPolygon(group, path);
std::reverse(path.begin(), path.end());
BuildNormals(path);
OffsetPolygon(group, path);
}
void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type)
{
group.path_.clear();
// do the line start cap
switch (end_type)
{
case EndType::Butt:
group.path_.push_back(Point64(
path[0].x - norms[0].x * group_delta_,
path[0].y - norms[0].y * group_delta_));
group.path_.push_back(GetPerpendic(path[0], norms[0], group_delta_));
break;
case EndType::Round:
DoRound(group, path, 0,0, PI);
break;
default:
DoSquare(group, path, 0, 0);
break;
}
size_t highI = path.size() - 1;
// offset the left side going forward
for (Path64::size_type i = 1, k = 0; i < highI; ++i)
OffsetPoint(group, path, i, k);
// reverse normals
for (size_t i = highI; i > 0; --i)
norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
norms[0] = norms[highI];
// do the line end cap
switch (end_type)
{
case EndType::Butt:
group.path_.push_back(Point64(
path[highI].x - norms[highI].x * group_delta_,
path[highI].y - norms[highI].y * group_delta_));
group.path_.push_back(GetPerpendic(path[highI], norms[highI], group_delta_));
break;
case EndType::Round:
DoRound(group, path, highI, highI, PI);
break;
default:
DoSquare(group, path, highI, highI);
break;
}
for (size_t i = highI, k = 0; i > 0; --i)
OffsetPoint(group, path, i, k, true);
group.paths_out_.push_back(group.path_);
}
void ClipperOffset::DoGroupOffset(Group& group, double delta)
{
if (group.end_type_ != EndType::Polygon) delta = std::abs(delta) * 0.5;
bool isClosedPaths = IsClosedPath(group.end_type_);
//the lowermost polygon must be an outer polygon. So we can use that as the
//designated orientation for outer polygons (needed for tidy-up clipping)
Rect64 r;
int idx = 0;
GetBoundsAndLowestPolyIdx(group.paths_in_, r, idx);
if (!IsSafeOffset(r, static_cast<int64_t>(std::ceil(delta))))
#if __cpp_exceptions
throw Clipper2Exception(range_error);
#else
error_code_ |= range_error_i;
return;
#endif
if (isClosedPaths)
{
double area = Area(group.paths_in_[idx]);
if (area == 0) return;
group.is_reversed_ = (area < 0);
if (group.is_reversed_) delta = -delta;
}
else
group.is_reversed_ = false;
group_delta_ = delta;
abs_group_delta_ = std::abs(group_delta_);
join_type_ = group.join_type_;
double arcTol = (arc_tolerance_ > floating_point_tolerance ? arc_tolerance_
: std::log10(2 + abs_group_delta_) * default_arc_tolerance); // empirically derived
//calculate a sensible number of steps (for 360 deg for the given offset
if (group.join_type_ == JoinType::Round || group.end_type_ == EndType::Round)
{
steps_per_rad_ = PI / std::acos(1 - arcTol / abs_group_delta_) / (PI *2);
}
bool is_closed_path = IsClosedPath(group.end_type_);
Paths64::const_iterator path_iter;
for(path_iter = group.paths_in_.cbegin(); path_iter != group.paths_in_.cend(); ++path_iter)
{
Path64 path = StripDuplicates(*path_iter, is_closed_path);
Path64::size_type cnt = path.size();
if (cnt == 0) continue;
if (cnt == 1) // single point - only valid with open paths
{
group.path_ = Path64();
//single vertex so build a circle or square ...
if (group.join_type_ == JoinType::Round)
{
double radius = abs_group_delta_;
group.path_ = Ellipse(path[0], radius, radius);
}
else
{
int d = (int)std::ceil(abs_group_delta_);
r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d);
group.path_ = r.AsPath();
}
group.paths_out_.push_back(group.path_);
}
else
{
BuildNormals(path);
if (group.end_type_ == EndType::Polygon) OffsetPolygon(group, path);
else if (group.end_type_ == EndType::Joined) OffsetOpenJoined(group, path);
else OffsetOpenPath(group, path, group.end_type_);
}
}
solution.reserve(solution.size() + group.paths_out_.size());
copy(group.paths_out_.begin(), group.paths_out_.end(), back_inserter(solution));
group.paths_out_.clear();
}
Paths64 ClipperOffset::Execute(double delta)
{
error_code_ = 0;
solution.clear();
if (groups_.size() == 0) return solution;
if (std::abs(delta) < default_arc_tolerance)
{
for (const Group& group : groups_)
{
solution.reserve(solution.size() + group.paths_in_.size());
copy(group.paths_in_.begin(), group.paths_in_.end(), back_inserter(solution));
}
return solution;
}
temp_lim_ = (miter_limit_ <= 1) ?
2.0 :
2.0 / (miter_limit_ * miter_limit_);
std::vector<Group>::iterator git;
for (git = groups_.begin(); git != groups_.end(); ++git)
{
DoGroupOffset(*git, delta);
if (!error_code_) continue;
solution.clear();
return solution;
}
//clean up self-intersections ...
Clipper64 c;
c.PreserveCollinear = false;
//the solution should retain the orientation of the input
c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed_;
c.AddSubject(solution);
if (groups_[0].is_reversed_)
c.Execute(ClipType::Union, FillRule::Negative, solution);
else
c.Execute(ClipType::Union, FillRule::Positive, solution);
return solution;
}
} // namespace

View File

@ -0,0 +1,513 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 14 January 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 *
* Purpose : FAST rectangular clipping *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#include <cmath>
#include "clipper2/clipper.h"
#include "clipper2/clipper.rectclip.h"
namespace Clipper2Lib {
//------------------------------------------------------------------------------
// Miscellaneous methods
//------------------------------------------------------------------------------
inline PointInPolygonResult Path1ContainsPath2(const Path64& path1, const Path64& path2)
{
PointInPolygonResult result = PointInPolygonResult::IsOn;
for(const Point64& pt : path2)
{
result = PointInPolygon(pt, path1);
if (result != PointInPolygonResult::IsOn) break;
}
return result;
}
inline bool GetLocation(const Rect64& rec,
const Point64& pt, Location& loc)
{
if (pt.x == rec.left && pt.y >= rec.top && pt.y <= rec.bottom)
{
loc = Location::Left;
return false;
}
else if (pt.x == rec.right && pt.y >= rec.top && pt.y <= rec.bottom)
{
loc = Location::Right;
return false;
}
else if (pt.y == rec.top && pt.x >= rec.left && pt.x <= rec.right)
{
loc = Location::Top;
return false;
}
else if (pt.y == rec.bottom && pt.x >= rec.left && pt.x <= rec.right)
{
loc = Location::Bottom;
return false;
}
else if (pt.x < rec.left) loc = Location::Left;
else if (pt.x > rec.right) loc = Location::Right;
else if (pt.y < rec.top) loc = Location::Top;
else if (pt.y > rec.bottom) loc = Location::Bottom;
else loc = Location::Inside;
return true;
}
inline bool GetIntersection(const Path64& rectPath,
const Point64& p, const Point64& p2, Location& loc, Point64& ip)
{
// gets the intersection closest to 'p'
// when Result = false, loc will remain unchanged
switch (loc)
{
case Location::Left:
if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip);
else if (p.y < rectPath[0].y &&
SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
{
GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip);
loc = Location::Top;
}
else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
{
GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip);
loc = Location::Bottom;
}
else return false;
break;
case Location::Top:
if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip);
else if (p.x < rectPath[0].x &&
SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
{
GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip);
loc = Location::Left;
}
else if (p.x > rectPath[1].x &&
SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
{
GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip);
loc = Location::Right;
}
else return false;
break;
case Location::Right:
if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip);
else if (p.y < rectPath[0].y &&
SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
{
GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip);
loc = Location::Top;
}
else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
{
GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip);
loc = Location::Bottom;
}
else return false;
break;
case Location::Bottom:
if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip);
else if (p.x < rectPath[3].x &&
SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
{
GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip);
loc = Location::Left;
}
else if (p.x > rectPath[2].x &&
SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
{
GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip);
loc = Location::Right;
}
else return false;
break;
default: // loc == rInside
if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
{
GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip);
loc = Location::Left;
}
else if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
{
GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip);
loc = Location::Top;
}
else if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
{
GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip);
loc = Location::Right;
}
else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
{
GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip);
loc = Location::Bottom;
}
else return false;
break;
}
return true;
}
inline Location GetAdjacentLocation(Location loc, bool isClockwise)
{
int delta = (isClockwise) ? 1 : 3;
return static_cast<Location>((static_cast<int>(loc) + delta) % 4);
}
inline bool HeadingClockwise(Location prev, Location curr)
{
return (static_cast<int>(prev) + 1) % 4 == static_cast<int>(curr);
}
inline bool AreOpposites(Location prev, Location curr)
{
return abs(static_cast<int>(prev) - static_cast<int>(curr)) == 2;
}
inline bool IsClockwise(Location prev, Location curr,
const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp)
{
if (AreOpposites(prev, curr))
return CrossProduct(prev_pt, rect_mp, curr_pt) < 0;
else
return HeadingClockwise(prev, curr);
}
//----------------------------------------------------------------------------
// RectClip64
//----------------------------------------------------------------------------
void RectClip::AddCorner(Location prev, Location curr)
{
if (HeadingClockwise(prev, curr))
result_.push_back(rectPath_[static_cast<int>(prev)]);
else
result_.push_back(rectPath_[static_cast<int>(curr)]);
}
void RectClip::AddCorner(Location& loc, bool isClockwise)
{
if (isClockwise)
{
result_.push_back(rectPath_[static_cast<int>(loc)]);
loc = GetAdjacentLocation(loc, true);
}
else
{
loc = GetAdjacentLocation(loc, false);
result_.push_back(rectPath_[static_cast<int>(loc)]);
}
}
void RectClip::GetNextLocation(const Path64& path,
Location& loc, int& i, int highI)
{
switch (loc)
{
case Location::Left:
while (i <= highI && path[i].x <= rect_.left) ++i;
if (i > highI) break;
else if (path[i].x >= rect_.right) loc = Location::Right;
else if (path[i].y <= rect_.top) loc = Location::Top;
else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
else loc = Location::Inside;
break;
case Location::Top:
while (i <= highI && path[i].y <= rect_.top) ++i;
if (i > highI) break;
else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
else if (path[i].x <= rect_.left) loc = Location::Left;
else if (path[i].x >= rect_.right) loc = Location::Right;
else loc = Location::Inside;
break;
case Location::Right:
while (i <= highI && path[i].x >= rect_.right) ++i;
if (i > highI) break;
else if (path[i].x <= rect_.left) loc = Location::Left;
else if (path[i].y <= rect_.top) loc = Location::Top;
else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
else loc = Location::Inside;
break;
case Location::Bottom:
while (i <= highI && path[i].y >= rect_.bottom) ++i;
if (i > highI) break;
else if (path[i].y <= rect_.top) loc = Location::Top;
else if (path[i].x <= rect_.left) loc = Location::Left;
else if (path[i].x >= rect_.right) loc = Location::Right;
else loc = Location::Inside;
break;
case Location::Inside:
while (i <= highI)
{
if (path[i].x < rect_.left) loc = Location::Left;
else if (path[i].x > rect_.right) loc = Location::Right;
else if (path[i].y > rect_.bottom) loc = Location::Bottom;
else if (path[i].y < rect_.top) loc = Location::Top;
else { result_.push_back(path[i]); ++i; continue; }
break; //inner loop
}
break;
} //switch
}
Path64 RectClip::Execute(const Path64& path)
{
if (rect_.IsEmpty() || path.size() < 3) return Path64();
result_.clear();
start_locs_.clear();
int i = 0, highI = static_cast<int>(path.size()) - 1;
Location prev = Location::Inside, loc;
Location crossing_loc = Location::Inside;
Location first_cross_ = Location::Inside;
if (!GetLocation(rect_, path[highI], loc))
{
i = highI - 1;
while (i >= 0 && !GetLocation(rect_, path[i], prev)) --i;
if (i < 0) return path;
if (prev == Location::Inside) loc = Location::Inside;
i = 0;
}
Location starting_loc = loc;
///////////////////////////////////////////////////
while (i <= highI)
{
prev = loc;
Location crossing_prev = crossing_loc;
GetNextLocation(path, loc, i, highI);
if (i > highI) break;
Point64 ip, ip2;
Point64 prev_pt = (i) ? path[static_cast<size_t>(i - 1)] : path[highI];
crossing_loc = loc;
if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip))
{
// ie remaining outside
if (crossing_prev == Location::Inside)
{
bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_);
do {
start_locs_.push_back(prev);
prev = GetAdjacentLocation(prev, isClockw);
} while (prev != loc);
crossing_loc = crossing_prev; // still not crossed
}
else if (prev != Location::Inside && prev != loc)
{
bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_);
do {
AddCorner(prev, isClockw);
} while (prev != loc);
}
++i;
continue;
}
////////////////////////////////////////////////////
// we must be crossing the rect boundary to get here
////////////////////////////////////////////////////
if (loc == Location::Inside) // path must be entering rect
{
if (first_cross_ == Location::Inside)
{
first_cross_ = crossing_loc;
start_locs_.push_back(prev);
}
else if (prev != crossing_loc)
{
bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], mp_);
do {
AddCorner(prev, isClockw);
} while (prev != crossing_loc);
}
}
else if (prev != Location::Inside)
{
// passing right through rect. 'ip' here will be the second
// intersect pt but we'll also need the first intersect pt (ip2)
loc = prev;
GetIntersection(rectPath_, prev_pt, path[i], loc, ip2);
if (crossing_prev != Location::Inside)
AddCorner(crossing_prev, loc);
if (first_cross_ == Location::Inside)
{
first_cross_ = loc;
start_locs_.push_back(prev);
}
loc = crossing_loc;
result_.push_back(ip2);
if (ip == ip2)
{
// it's very likely that path[i] is on rect
GetLocation(rect_, path[i], loc);
AddCorner(crossing_loc, loc);
crossing_loc = loc;
continue;
}
}
else // path must be exiting rect
{
loc = crossing_loc;
if (first_cross_ == Location::Inside)
first_cross_ = crossing_loc;
}
result_.push_back(ip);
} //while i <= highI
///////////////////////////////////////////////////
if (first_cross_ == Location::Inside)
{
if (starting_loc == Location::Inside) return path;
Rect64 tmp_rect = Bounds(path);
if (tmp_rect.Contains(rect_) &&
Path1ContainsPath2(path, rectPath_) !=
PointInPolygonResult::IsOutside) return rectPath_;
else
return Path64();
}
if (loc != Location::Inside &&
(loc != first_cross_ || start_locs_.size() > 2))
{
if (start_locs_.size() > 0)
{
prev = loc;
for (auto loc2 : start_locs_)
{
if (prev == loc2) continue;
AddCorner(prev, HeadingClockwise(prev, loc2));
prev = loc2;
}
loc = prev;
}
if (loc != first_cross_)
AddCorner(loc, HeadingClockwise(loc, first_cross_));
}
if (result_.size() < 3) return Path64();
// tidy up duplicates and collinear segments
Path64 res;
res.reserve(result_.size());
size_t k = 0; highI = static_cast<int>(result_.size()) - 1;
Point64 prev_pt = result_[highI];
res.push_back(result_[0]);
Path64::const_iterator cit;
for (cit = result_.cbegin() + 1; cit != result_.cend(); ++cit)
{
if (CrossProduct(prev_pt, res[k], *cit))
{
prev_pt = res[k++];
res.push_back(*cit);
}
else
res[k] = *cit;
}
if (k < 2) return Path64();
// and a final check for collinearity
else if (!CrossProduct(res[0], res[k - 1], res[k])) res.pop_back();
return res;
}
Paths64 RectClipLines::Execute(const Path64& path)
{
result_.clear();
Paths64 result;
if (rect_.IsEmpty() || path.size() == 0) return result;
int i = 1, highI = static_cast<int>(path.size()) - 1;
Location prev = Location::Inside, loc;
Location crossing_loc;
if (!GetLocation(rect_, path[0], loc))
{
while (i <= highI && !GetLocation(rect_, path[i], prev)) ++i;
if (i > highI) {
result.push_back(path);
return result;
}
if (prev == Location::Inside) loc = Location::Inside;
i = 1;
}
if (loc == Location::Inside) result_.push_back(path[0]);
///////////////////////////////////////////////////
while (i <= highI)
{
prev = loc;
GetNextLocation(path, loc, i, highI);
if (i > highI) break;
Point64 ip, ip2;
Point64 prev_pt = path[static_cast<size_t>(i - 1)];
crossing_loc = loc;
if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip))
{
// ie remaining outside
++i;
continue;
}
////////////////////////////////////////////////////
// we must be crossing the rect boundary to get here
////////////////////////////////////////////////////
if (loc == Location::Inside) // path must be entering rect
{
result_.push_back(ip);
}
else if (prev != Location::Inside)
{
// passing right through rect. 'ip' here will be the second
// intersect pt but we'll also need the first intersect pt (ip2)
crossing_loc = prev;
GetIntersection(rectPath_, prev_pt, path[i], crossing_loc, ip2);
result_.push_back(ip2);
result_.push_back(ip);
result.push_back(result_);
result_.clear();
}
else // path must be exiting rect
{
result_.push_back(ip);
result.push_back(result_);
result_.clear();
}
} //while i <= highI
///////////////////////////////////////////////////
if (result_.size() > 1)
result.push_back(result_);
return result;
}
} // namespace

View File

@ -0,0 +1,7 @@
#include "register_types.h"
void register_clipper2_types(ModuleRegistrationLevel p_level) {
}
void unregister_clipper2_types(ModuleRegistrationLevel p_level) {
}

View File

@ -0,0 +1,9 @@
#ifndef CLIPPER2_REGISTER_TYPES_H
#define CLIPPER2_REGISTER_TYPES_H
#include "modules/register_module_types.h"
void register_clipper2_types(ModuleRegistrationLevel p_level);
void unregister_clipper2_types(ModuleRegistrationLevel p_level);
#endif