ENH: update Clipper2 to 1.5.2

Clipper2 has added a lot of features in the past 2 years, including "offset to polytree" which is very useful for expolygons.

jira: none
Change-Id: I6f51e243656312d8c7693e1c9f5c52cf2f0034d1
This commit is contained in:
Arthur 2025-02-10 10:17:25 +08:00 committed by lane.wei
parent 21d1159cd5
commit c486b42e9c
12 changed files with 6559 additions and 5828 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,19 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 4 November 2022 * * Date : 17 September 2024 *
* Website : http://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 * * Copyright : Angus Johnson 2010-2024 *
* Purpose : This is the main polygon clipping module * * Purpose : This is the main polygon clipping module *
* License : http://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
#ifndef CLIPPER_ENGINE_H #ifndef CLIPPER_ENGINE_H
#define CLIPPER_ENGINE_H #define CLIPPER_ENGINE_H
constexpr auto CLIPPER2_VERSION = "1.0.6"; #include "clipper2/clipper.core.h"
#include <cstdlib>
#include <queue> #include <queue>
#include <stdexcept>
#include <vector>
#include <functional> #include <functional>
#include <cstddef> #include <memory>
#include <cstdint>
#include "clipper.core.h"
namespace Clipper2Lib { namespace Clipper2Lib {
@ -29,15 +23,16 @@ namespace Clipper2Lib {
struct Vertex; struct Vertex;
struct LocalMinima; struct LocalMinima;
struct OutRec; struct OutRec;
struct Joiner; struct HorzSegment;
//Note: all clipping operations except for Difference are commutative. //Note: all clipping operations except for Difference are commutative.
enum class ClipType { None, Intersection, Union, Difference, Xor }; enum class ClipType { NoClip, Intersection, Union, Difference, Xor };
enum class PathType { Subject, Clip }; enum class PathType { Subject, Clip };
enum class JoinWith { NoJoin, Left, Right };
enum class VertexFlags : uint32_t { enum class VertexFlags : uint32_t {
None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8 Empty = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8
}; };
constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b) constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b)
@ -54,7 +49,7 @@ namespace Clipper2Lib {
Point64 pt; Point64 pt;
Vertex* next = nullptr; Vertex* next = nullptr;
Vertex* prev = nullptr; Vertex* prev = nullptr;
VertexFlags flags = VertexFlags::None; VertexFlags flags = VertexFlags::Empty;
}; };
struct OutPt { struct OutPt {
@ -62,7 +57,7 @@ namespace Clipper2Lib {
OutPt* next = nullptr; OutPt* next = nullptr;
OutPt* prev = nullptr; OutPt* prev = nullptr;
OutRec* outrec; OutRec* outrec;
Joiner* joiner = nullptr; HorzSegment* horz = nullptr;
OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) { OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) {
next = this; next = this;
@ -84,15 +79,21 @@ namespace Clipper2Lib {
struct OutRec { struct OutRec {
size_t idx = 0; size_t idx = 0;
OutRec* owner = nullptr; OutRec* owner = nullptr;
OutRecList* splits = nullptr;
Active* front_edge = nullptr; Active* front_edge = nullptr;
Active* back_edge = nullptr; Active* back_edge = nullptr;
OutPt* pts = nullptr; OutPt* pts = nullptr;
PolyPath* polypath = nullptr; PolyPath* polypath = nullptr;
OutRecList* splits = nullptr;
OutRec* recursive_split = nullptr;
Rect64 bounds = {}; Rect64 bounds = {};
Path64 path; Path64 path;
bool is_open = false; bool is_open = false;
~OutRec() { if (splits) delete splits; };
~OutRec() {
if (splits) delete splits;
// nb: don't delete the split pointers
// as these are owned by ClipperBase's outrec_list_
};
}; };
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////
@ -124,6 +125,7 @@ namespace Clipper2Lib {
Vertex* vertex_top = nullptr; Vertex* vertex_top = nullptr;
LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti) LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti)
bool is_left_bound = false; bool is_left_bound = false;
JoinWith join_with = JoinWith::NoJoin;
}; };
struct LocalMinima { struct LocalMinima {
@ -138,26 +140,58 @@ namespace Clipper2Lib {
Point64 pt; Point64 pt;
Active* edge1; Active* edge1;
Active* edge2; Active* edge2;
IntersectNode() : pt(Point64(0, 0)), edge1(NULL), edge2(NULL) {} IntersectNode() : pt(Point64(0,0)), edge1(NULL), edge2(NULL) {}
IntersectNode(Active* e1, Active* e2, Point64& pt_) : IntersectNode(Active* e1, Active* e2, Point64& pt_) :
pt(pt_), edge1(e1), edge2(e2) 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 #ifdef USINGZ
typedef std::function<void(const Point64& e1bot, const Point64& e1top, typedef std::function<void(const Point64& e1bot, const Point64& e1top,
const Point64& e2bot, const Point64& e2top, Point64& pt)> ZCallback64; const Point64& e2bot, const Point64& e2top, Point64& pt)> ZCallback64;
typedef std::function<void(const PointD& e1bot, const PointD& e1top, typedef std::function<void(const PointD& e1bot, const PointD& e1top,
const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD; const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD;
#endif #endif
typedef std::vector<HorzSegment> HorzSegmentList;
typedef std::unique_ptr<LocalMinima> LocalMinima_ptr;
typedef std::vector<LocalMinima_ptr> LocalMinimaList;
typedef std::vector<IntersectNode> IntersectNodeList;
// ReuseableDataContainer64 ------------------------------------------------
class ReuseableDataContainer64 {
private:
friend class ClipperBase;
LocalMinimaList minima_list_;
std::vector<Vertex*> vertex_lists_;
void AddLocMin(Vertex& vert, PathType polytype, bool is_open);
public:
virtual ~ReuseableDataContainer64();
void Clear();
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
};
// ClipperBase ------------------------------------------------------------- // ClipperBase -------------------------------------------------------------
class ClipperBase { class ClipperBase {
private: private:
ClipType cliptype_ = ClipType::None; ClipType cliptype_ = ClipType::NoClip;
FillRule fillrule_ = FillRule::EvenOdd; FillRule fillrule_ = FillRule::EvenOdd;
FillRule fillpos = FillRule::Positive; FillRule fillpos = FillRule::Positive;
int64_t bot_y_ = 0; int64_t bot_y_ = 0;
@ -165,21 +199,21 @@ namespace Clipper2Lib {
bool using_polytree_ = false; bool using_polytree_ = false;
Active* actives_ = nullptr; Active* actives_ = nullptr;
Active *sel_ = nullptr; Active *sel_ = nullptr;
Joiner *horz_joiners_ = nullptr; LocalMinimaList minima_list_; //pointers in case of memory reallocs
std::vector<LocalMinima*> minima_list_; //pointers in case of memory reallocs LocalMinimaList::iterator current_locmin_iter_;
std::vector<LocalMinima*>::iterator current_locmin_iter_;
std::vector<Vertex*> vertex_lists_; std::vector<Vertex*> vertex_lists_;
std::priority_queue<int64_t> scanline_list_; std::priority_queue<int64_t> scanline_list_;
std::vector<IntersectNode> intersect_nodes_; IntersectNodeList intersect_nodes_;
std::vector<Joiner*> joiner_list_; //pointers in case of memory reallocs HorzSegmentList horz_seg_list_;
std::vector<HorzJoin> horz_join_list_;
void Reset(); void Reset();
void InsertScanline(int64_t y); inline void InsertScanline(int64_t y);
bool PopScanline(int64_t &y); inline bool PopScanline(int64_t &y);
bool PopLocalMinima(int64_t y, LocalMinima *&local_minima); inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima);
void DisposeAllOutRecs(); void DisposeAllOutRecs();
void DisposeVerticesAndLocalMinima(); void DisposeVerticesAndLocalMinima();
void DeleteEdges(Active*& e); void DeleteEdges(Active*& e);
void AddLocMin(Vertex &vert, PathType polytype, bool is_open); inline void AddLocMin(Vertex &vert, PathType polytype, bool is_open);
bool IsContributingClosed(const Active &e) const; bool IsContributingClosed(const Active &e) const;
inline bool IsContributingOpen(const Active &e) const; inline bool IsContributingOpen(const Active &e) const;
void SetWindCountForClosedPathEdge(Active &edge); void SetWindCountForClosedPathEdge(Active &edge);
@ -190,7 +224,7 @@ namespace Clipper2Lib {
inline bool PopHorz(Active *&e); inline bool PopHorz(Active *&e);
inline OutPt* StartOpenPath(Active &e, const Point64& pt); inline OutPt* StartOpenPath(Active &e, const Point64& pt);
inline void UpdateEdgeIntoAEL(Active *e); inline void UpdateEdgeIntoAEL(Active *e);
OutPt* IntersectEdges(Active &e1, Active &e2, const Point64& pt); void IntersectEdges(Active &e1, Active &e2, const Point64& pt);
inline void DeleteFromAEL(Active &e); inline void DeleteFromAEL(Active &e);
inline void AdjustCurrXAndCopyToSEL(const int64_t top_y); inline void AdjustCurrXAndCopyToSEL(const int64_t top_y);
void DoIntersections(const int64_t top_y); void DoIntersections(const int64_t top_y);
@ -198,38 +232,41 @@ namespace Clipper2Lib {
bool BuildIntersectList(const int64_t top_y); bool BuildIntersectList(const int64_t top_y);
void ProcessIntersectList(); void ProcessIntersectList();
void SwapPositionsInAEL(Active& edge1, Active& edge2); void SwapPositionsInAEL(Active& edge1, Active& edge2);
OutRec* NewOutRec();
OutPt* AddOutPt(const Active &e, const Point64& pt); OutPt* AddOutPt(const Active &e, const Point64& pt);
OutPt* AddLocalMinPoly(Active &e1, Active &e2, OutPt* AddLocalMinPoly(Active &e1, Active &e2,
const Point64& pt, bool is_new = false); const Point64& pt, bool is_new = false);
OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt); OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt);
void DoHorizontal(Active &horz); void DoHorizontal(Active &horz);
bool ResetHorzDirection(const Active &horz, const Active *max_pair, bool ResetHorzDirection(const Active &horz, const Vertex* max_vertex,
int64_t &horz_left, int64_t &horz_right); int64_t &horz_left, int64_t &horz_right);
void DoTopOfScanbeam(const int64_t top_y); void DoTopOfScanbeam(const int64_t top_y);
Active *DoMaxima(Active &e); Active *DoMaxima(Active &e);
void JoinOutrecPaths(Active &e1, Active &e2); void JoinOutrecPaths(Active &e1, Active &e2);
void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec);
bool ValidateClosedPathEx(OutPt*& outrec);
void CleanCollinear(OutRec* outrec);
void FixSelfIntersects(OutRec* outrec); void FixSelfIntersects(OutRec* outrec);
void DoSplitOp(OutRec* outRec, OutPt* splitOp); void DoSplitOp(OutRec* outRec, OutPt* splitOp);
Joiner* GetHorzTrialParent(const OutPt* op);
bool OutPtInTrialHorzList(OutPt* op); inline void AddTrialHorzJoin(OutPt* op);
void SafeDisposeOutPts(OutPt*& op); void ConvertHorzSegsToJoins();
void SafeDeleteOutPtJoiners(OutPt* op); void ProcessHorzJoins();
void AddTrialHorzJoin(OutPt* op);
void DeleteTrialHorzJoin(OutPt* op); void Split(Active& e, const Point64& pt);
void ConvertHorzTrialsToJoins(); inline void CheckJoinLeft(Active& e,
void AddJoin(OutPt* op1, OutPt* op2); const Point64& pt, bool check_curr_x = false);
void DeleteJoin(Joiner* joiner); inline void CheckJoinRight(Active& e,
void ProcessJoinerList(); const Point64& pt, bool check_curr_x = false);
OutRec* ProcessJoin(Joiner* joiner);
protected: protected:
bool preserve_collinear_ = true;
bool reverse_solution_ = false;
int error_code_ = 0;
bool has_open_paths_ = false; bool has_open_paths_ = false;
bool succeeded_ = true; bool succeeded_ = true;
std::vector<OutRec*> outrec_list_; //pointers in case list memory reallocated OutRecList outrec_list_; //pointers in case list memory reallocated
bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees); bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
bool DeepCheckOwner(OutRec* outrec, OutRec* owner); void CleanCollinear(OutRec* outrec);
bool CheckBounds(OutRec* outrec);
bool CheckSplitOwner(OutRec* outrec, OutRecList* splits);
void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath);
#ifdef USINGZ #ifdef USINGZ
ZCallback64 zCallback_ = nullptr; ZCallback64 zCallback_ = nullptr;
void SetZ(const Active& e1, const Active& e2, Point64& pt); void SetZ(const Active& e1, const Active& e2, Point64& pt);
@ -239,9 +276,16 @@ namespace Clipper2Lib {
void AddPaths(const Paths64& paths, PathType polytype, bool is_open); void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
public: public:
virtual ~ClipperBase(); virtual ~ClipperBase();
bool PreserveCollinear = true; int ErrorCode() const { return error_code_; };
bool ReverseSolution = false; void PreserveCollinear(bool val) { preserve_collinear_ = val; };
bool PreserveCollinear() const { return preserve_collinear_;};
void ReverseSolution(bool val) { reverse_solution_ = val; };
bool ReverseSolution() const { return reverse_solution_; };
void Clear(); void Clear();
void AddReuseableData(const ReuseableDataContainer64& reuseable_data);
#ifdef USINGZ
int64_t DefaultZ = 0;
#endif
}; };
// PolyPath / PolyTree -------------------------------------------------------- // PolyPath / PolyTree --------------------------------------------------------
@ -256,7 +300,7 @@ namespace Clipper2Lib {
PolyPath* parent_; PolyPath* parent_;
public: public:
PolyPath(PolyPath* parent = nullptr): parent_(parent){} PolyPath(PolyPath* parent = nullptr): parent_(parent){}
virtual ~PolyPath() { Clear(); }; virtual ~PolyPath() {};
//https://en.cppreference.com/w/cpp/language/rule_of_three //https://en.cppreference.com/w/cpp/language/rule_of_three
PolyPath(const PolyPath&) = delete; PolyPath(const PolyPath&) = delete;
PolyPath& operator=(const PolyPath&) = delete; PolyPath& operator=(const PolyPath&) = delete;
@ -271,50 +315,58 @@ namespace Clipper2Lib {
virtual PolyPath* AddChild(const Path64& path) = 0; virtual PolyPath* AddChild(const Path64& path) = 0;
virtual void Clear() {}; virtual void Clear() = 0;
virtual size_t Count() const { return 0; } virtual size_t Count() const { return 0; }
const PolyPath* Parent() const { return parent_; } const PolyPath* Parent() const { return parent_; }
bool IsHole() const bool IsHole() const
{ {
const PolyPath* pp = parent_; unsigned lvl = Level();
bool is_hole = pp; //Even levels except level 0
while (pp) { return lvl && !(lvl & 1);
is_hole = !is_hole;
pp = pp->parent_;
}
return is_hole;
} }
}; };
typedef typename std::vector<std::unique_ptr<PolyPath64>> PolyPath64List;
typedef typename std::vector<std::unique_ptr<PolyPathD>> PolyPathDList;
class PolyPath64 : public PolyPath { class PolyPath64 : public PolyPath {
private: private:
std::vector<PolyPath64*> childs_; PolyPath64List childs_;
Path64 polygon_; Path64 polygon_;
typedef typename std::vector<PolyPath64*>::const_iterator pp64_itor;
public: public:
PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {} explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
PolyPath64 *operator[](size_t index) { return static_cast<PolyPath64 *>(childs_[index]); } explicit PolyPath64(PolyPath64* parent, const Path64& path) : PolyPath(parent) { polygon_ = path; }
PolyPath64 *Childs(size_t index) const { return static_cast<PolyPath64 *>(childs_[index]); }
pp64_itor begin() const { return childs_.cbegin(); } ~PolyPath64() {
pp64_itor end() const { return childs_.cend(); } childs_.resize(0);
}
PolyPath64* operator [] (size_t index) const
{
return childs_[index].get(); //std::unique_ptr
}
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 PolyPath64* AddChild(const Path64& path) override
{ {
PolyPath64* result = new PolyPath64(this); return childs_.emplace_back(std::make_unique<PolyPath64>(this, path)).get();
childs_.push_back(result);
result->polygon_ = path;
return result;
} }
void Clear() override void Clear() override
{ {
for (const PolyPath64* child : childs_) delete child;
childs_.resize(0); childs_.resize(0);
} }
size_t Count() const override size_t Count() const override
{ {
return childs_.size(); return childs_.size();
} }
@ -323,78 +375,73 @@ namespace Clipper2Lib {
double Area() const double Area() const
{ {
double result = Clipper2Lib::Area<int64_t>(polygon_); return std::accumulate(childs_.cbegin(), childs_.cend(),
for (const PolyPath64* child : childs_) Clipper2Lib::Area<int64_t>(polygon_),
result += child->Area(); [](double a, const auto& child) {return a + child->Area(); });
return result;
}
friend std::ostream& operator << (std::ostream& outstream, const PolyPath64& polypath)
{
const size_t level_indent = 4;
const size_t coords_per_line = 4;
const size_t last_on_line = coords_per_line - 1;
unsigned level = polypath.Level();
if (level > 0)
{
std::string level_padding;
level_padding.insert(0, (level - 1) * level_indent, ' ');
std::string caption = polypath.IsHole() ? "Hole " : "Outer Polygon ";
std::string childs = polypath.Count() == 1 ? " child" : " children";
outstream << level_padding.c_str() << caption << "with " << polypath.Count() << childs << std::endl;
outstream << level_padding;
size_t i = 0, highI = polypath.Polygon().size() - 1;
for (; i < highI; ++i)
{
outstream << polypath.Polygon()[i] << ' ';
if ((i % coords_per_line) == last_on_line)
outstream << std::endl << level_padding;
}
if (highI > 0) outstream << polypath.Polygon()[i];
outstream << std::endl;
}
for (auto child : polypath)
outstream << *child;
return outstream;
} }
}; };
class PolyPathD : public PolyPath { class PolyPathD : public PolyPath {
private: private:
std::vector<PolyPathD*> childs_; PolyPathDList childs_;
double inv_scale_; double scale_;
PathD polygon_; PathD polygon_;
typedef typename std::vector<PolyPathD*>::const_iterator ppD_itor;
public: public:
PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent)
{ {
inv_scale_ = parent ? parent->inv_scale_ : 1.0; scale_ = parent ? parent->scale_ : 1.0;
} }
PolyPathD* operator [] (size_t index)
{
return static_cast<PolyPathD*>(childs_[index]);
}
ppD_itor begin() const { return childs_.cbegin(); }
ppD_itor end() const { return childs_.cend(); }
void SetInvScale(double value) { inv_scale_ = value; } explicit PolyPathD(PolyPathD* parent, const Path64& path) : PolyPath(parent)
double InvScale() { return inv_scale_; } {
scale_ = parent ? parent->scale_ : 1.0;
int error_code = 0;
polygon_ = ScalePath<double, int64_t>(path, scale_, error_code);
}
explicit PolyPathD(PolyPathD* parent, const PathD& path) : PolyPath(parent)
{
scale_ = parent ? parent->scale_ : 1.0;
polygon_ = path;
}
~PolyPathD() {
childs_.resize(0);
}
PolyPathD* operator [] (size_t index) const
{
return childs_[index].get();
}
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 SetScale(double value) { scale_ = value; }
double Scale() const { return scale_; }
PolyPathD* AddChild(const Path64& path) override PolyPathD* AddChild(const Path64& path) override
{ {
PolyPathD* result = new PolyPathD(this); return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
childs_.push_back(result); }
result->polygon_ = ScalePath<double, int64_t>(path, inv_scale_);
return result; PolyPathD* AddChild(const PathD& path)
{
return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
} }
void Clear() override void Clear() override
{ {
for (const PolyPathD* child : childs_) delete child;
childs_.resize(0); childs_.resize(0);
} }
size_t Count() const override size_t Count() const override
{ {
return childs_.size(); return childs_.size();
} }
@ -403,10 +450,9 @@ namespace Clipper2Lib {
double Area() const double Area() const
{ {
double result = Clipper2Lib::Area<double>(polygon_); return std::accumulate(childs_.begin(), childs_.end(),
for (const PolyPathD* child : childs_) Clipper2Lib::Area<double>(polygon_),
result += child->Area(); [](double a, const auto& child) {return a + child->Area(); });
return result;
} }
}; };
@ -446,7 +492,7 @@ namespace Clipper2Lib {
closed_paths.clear(); closed_paths.clear();
open_paths.clear(); open_paths.clear();
if (ExecuteInternal(clip_type, fill_rule, false)) if (ExecuteInternal(clip_type, fill_rule, false))
BuildPaths64(closed_paths, &open_paths); BuildPaths64(closed_paths, &open_paths);
CleanUp(); CleanUp();
return succeeded_; return succeeded_;
} }
@ -475,14 +521,14 @@ namespace Clipper2Lib {
private: private:
double scale_ = 1.0, invScale_ = 1.0; double scale_ = 1.0, invScale_ = 1.0;
#ifdef USINGZ #ifdef USINGZ
ZCallbackD zCallback_ = nullptr; ZCallbackD zCallbackD_ = nullptr;
#endif #endif
void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen); void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen);
void BuildTreeD(PolyPathD& polytree, PathsD& open_paths); void BuildTreeD(PolyPathD& polytree, PathsD& open_paths);
public: public:
explicit ClipperD(int precision = 2) : ClipperBase() explicit ClipperD(int precision = 2) : ClipperBase()
{ {
CheckPrecision(precision); CheckPrecisionRange(precision, error_code_);
// to optimize scaling / descaling precision // to optimize scaling / descaling precision
// set the scale to a power of double's radix (2) (#25) // set the scale to a power of double's radix (2) (#25)
scale_ = std::pow(std::numeric_limits<double>::radix, scale_ = std::pow(std::numeric_limits<double>::radix,
@ -491,7 +537,7 @@ namespace Clipper2Lib {
} }
#ifdef USINGZ #ifdef USINGZ
void SetZCallback(ZCallbackD cb) { zCallback_ = cb; }; void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; };
void ZCB(const Point64& e1bot, const Point64& e1top, void ZCB(const Point64& e1bot, const Point64& e1top,
const Point64& e2bot, const Point64& e2top, Point64& pt) const Point64& e2bot, const Point64& e2top, Point64& pt)
@ -505,13 +551,13 @@ namespace Clipper2Lib {
PointD e1t = PointD(e1top) * invScale_; PointD e1t = PointD(e1top) * invScale_;
PointD e2b = PointD(e2bot) * invScale_; PointD e2b = PointD(e2bot) * invScale_;
PointD e2t = PointD(e2top) * invScale_; PointD e2t = PointD(e2top) * invScale_;
zCallback_(e1b,e1t, e2b, e2t, tmp); zCallbackD_(e1b,e1t, e2b, e2t, tmp);
pt.z = tmp.z; // only update 'z' pt.z = tmp.z; // only update 'z'
}; };
void CheckCallback() void CheckCallback()
{ {
if(zCallback_) if(zCallbackD_)
// if the user defined float point callback has been assigned // if the user defined float point callback has been assigned
// then assign the proxy callback function // then assign the proxy callback function
ClipperBase::zCallback_ = ClipperBase::zCallback_ =
@ -526,17 +572,17 @@ namespace Clipper2Lib {
void AddSubject(const PathsD& subjects) void AddSubject(const PathsD& subjects)
{ {
AddPaths(ScalePaths<int64_t, double>(subjects, scale_), PathType::Subject, false); AddPaths(ScalePaths<int64_t, double>(subjects, scale_, error_code_), PathType::Subject, false);
} }
void AddOpenSubject(const PathsD& open_subjects) void AddOpenSubject(const PathsD& open_subjects)
{ {
AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_), PathType::Subject, true); AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_, error_code_), PathType::Subject, true);
} }
void AddClip(const PathsD& clips) void AddClip(const PathsD& clips)
{ {
AddPaths(ScalePaths<int64_t, double>(clips, scale_), PathType::Clip, false); AddPaths(ScalePaths<int64_t, double>(clips, scale_, error_code_), PathType::Clip, false);
} }
bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths) bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths)
@ -574,7 +620,7 @@ namespace Clipper2Lib {
if (ExecuteInternal(clip_type, fill_rule, true)) if (ExecuteInternal(clip_type, fill_rule, true))
{ {
polytree.Clear(); polytree.Clear();
polytree.SetInvScale(invScale_); polytree.SetScale(invScale_);
open_paths.clear(); open_paths.clear();
BuildTreeD(polytree, open_paths); BuildTreeD(polytree, open_paths);
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,18 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 15 October 2022 * * Date : 1 November 2023 *
* Website : http://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 * * Copyright : Angus Johnson 2010-2023 *
* Purpose : Minkowski Sum and Difference * * Purpose : Minkowski Sum and Difference *
* License : http://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
#ifndef CLIPPER_MINKOWSKI_H #ifndef CLIPPER_MINKOWSKI_H
#define CLIPPER_MINKOWSKI_H #define CLIPPER_MINKOWSKI_H
#include <cstdlib> #include "clipper2/clipper.core.h"
#include <vector>
#include <string>
#include "clipper.core.h"
namespace Clipper2Lib namespace Clipper2Lib
{ {
namespace detail namespace detail
@ -35,7 +32,7 @@ namespace Clipper2Lib
Path64 path2(pattern.size()); Path64 path2(pattern.size());
std::transform(pattern.cbegin(), pattern.cend(), std::transform(pattern.cbegin(), pattern.cend(),
path2.begin(), [p](const Point64& pt2) {return p + pt2; }); path2.begin(), [p](const Point64& pt2) {return p + pt2; });
tmp.push_back(path2); tmp.emplace_back(std::move(path2));
} }
} }
else else
@ -45,7 +42,7 @@ namespace Clipper2Lib
Path64 path2(pattern.size()); Path64 path2(pattern.size());
std::transform(pattern.cbegin(), pattern.cend(), std::transform(pattern.cbegin(), pattern.cend(),
path2.begin(), [p](const Point64& pt2) {return p - pt2; }); path2.begin(), [p](const Point64& pt2) {return p - pt2; });
tmp.push_back(path2); tmp.emplace_back(std::move(path2));
} }
} }
@ -59,14 +56,14 @@ namespace Clipper2Lib
Path64 quad; Path64 quad;
quad.reserve(4); quad.reserve(4);
{ {
quad.push_back(tmp[g][h]); quad.emplace_back(tmp[g][h]);
quad.push_back(tmp[i][h]); quad.emplace_back(tmp[i][h]);
quad.push_back(tmp[i][j]); quad.emplace_back(tmp[i][j]);
quad.push_back(tmp[g][j]); quad.emplace_back(tmp[g][j]);
}; };
if (!IsPositive(quad)) if (!IsPositive(quad))
std::reverse(quad.begin(), quad.end()); std::reverse(quad.begin(), quad.end());
result.push_back(quad); result.emplace_back(std::move(quad));
h = j; h = j;
} }
g = i; g = i;
@ -92,11 +89,12 @@ namespace Clipper2Lib
inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2) inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
{ {
int error_code = 0;
double scale = pow(10, decimalPlaces); double scale = pow(10, decimalPlaces);
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale); Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
Path64 path64 = ScalePath<int64_t, double>(path, scale); Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero); Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero);
return ScalePaths<double, int64_t>(tmp, 1 / scale); return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
} }
inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed) inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed)
@ -106,11 +104,12 @@ namespace Clipper2Lib
inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2) inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2)
{ {
int error_code = 0;
double scale = pow(10, decimalPlaces); double scale = pow(10, decimalPlaces);
Path64 pat64 = ScalePath<int64_t, double>(pattern, scale); Path64 pat64 = ScalePath<int64_t, double>(pattern, scale, error_code);
Path64 path64 = ScalePath<int64_t, double>(path, scale); Path64 path64 = ScalePath<int64_t, double>(path, scale, error_code);
Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero); Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero);
return ScalePaths<double, int64_t>(tmp, 1 / scale); return ScalePaths<double, int64_t>(tmp, 1 / scale, error_code);
} }
} // Clipper2Lib namespace } // Clipper2Lib namespace

View File

@ -1,20 +1,24 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 15 October 2022 * * Date : 22 January 2025 *
* Website : http://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 * * Copyright : Angus Johnson 2010-2025 *
* Purpose : Path Offset (Inflate/Shrink) * * Purpose : Path Offset (Inflate/Shrink) *
* License : http://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
#ifndef CLIPPER_OFFSET_H_ #ifndef CLIPPER_OFFSET_H_
#define CLIPPER_OFFSET_H_ #define CLIPPER_OFFSET_H_
#include "clipper.core.h" #include "clipper.core.h"
#include "clipper.engine.h"
#include <optional>
namespace Clipper2Lib { namespace Clipper2Lib {
enum class JoinType { Square, Round, Miter }; enum class JoinType { Square, Bevel, Round, Miter };
//Square : Joins are 'squared' at exactly the offset distance (more complex code)
//Bevel : Similar to Square, but the offset distance varies with angle (simple code & faster)
enum class EndType {Polygon, Joined, Butt, Square, Round}; enum class EndType {Polygon, Joined, Butt, Square, Round};
//Butt : offsets both sides of a path, with square blunt ends //Butt : offsets both sides of a path, with square blunt ends
@ -23,49 +27,64 @@ enum class EndType {Polygon, Joined, Butt, Square, Round};
//Joined : offsets both sides of a path, with joined ends //Joined : offsets both sides of a path, with joined ends
//Polygon: offsets only one side of a closed path //Polygon: offsets only one side of a closed path
typedef std::function<double(const Path64& path, const PathD& path_normals, size_t curr_idx, size_t prev_idx)> DeltaCallback64;
class ClipperOffset { class ClipperOffset {
private: private:
class Group { class Group {
public: public:
Paths64 paths_in_; Paths64 paths_in;
Paths64 paths_out_; std::optional<size_t> lowest_path_idx{};
Path64 path_; bool is_reversed = false;
bool is_reversed_ = false; JoinType join_type;
JoinType join_type_; EndType end_type;
EndType end_type_; Group(const Paths64& _paths, 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 delta_ = 0.0;
double group_delta_ = 0.0; double group_delta_ = 0.0;
double abs_group_delta_ = 0.0;
double temp_lim_ = 0.0; double temp_lim_ = 0.0;
double steps_per_rad_ = 0.0; double steps_per_rad_ = 0.0;
double step_sin_ = 0.0;
double step_cos_ = 0.0;
PathD norms; PathD norms;
Paths64 solution; Path64 path_out;
Paths64* solution = nullptr;
PolyTree64* solution_tree = nullptr;
std::vector<Group> groups_; std::vector<Group> groups_;
JoinType join_type_ = JoinType::Square; JoinType join_type_ = JoinType::Bevel;
EndType end_type_ = EndType::Polygon;
double miter_limit_ = 0.0; double miter_limit_ = 0.0;
double arc_tolerance_ = 0.0; double arc_tolerance_ = 0.0;
bool merge_groups_ = true;
bool preserve_collinear_ = false; bool preserve_collinear_ = false;
bool reverse_solution_ = false; bool reverse_solution_ = false;
void DoSquare(Group& group, const Path64& path, size_t j, size_t k); #ifdef USINGZ
void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a); ZCallback64 zCallback64_ = nullptr;
void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle); void ZCB(const Point64& bot1, const Point64& top1,
const Point64& bot2, const Point64& top2, Point64& ip);
#endif
DeltaCallback64 deltaCallback64_ = nullptr;
size_t CalcSolutionCapacity();
bool CheckReverseOrientation();
void DoBevel(const Path64& path, size_t j, size_t k);
void DoSquare(const Path64& path, size_t j, size_t k);
void DoMiter(const Path64& path, size_t j, size_t k, double cos_a);
void DoRound(const Path64& path, size_t j, size_t k, double angle);
void BuildNormals(const Path64& path); void BuildNormals(const Path64& path);
void OffsetPolygon(Group& group, Path64& path); void OffsetPolygon(Group& group, const Path64& path);
void OffsetOpenJoined(Group& group, Path64& path); void OffsetOpenJoined(Group& group, const Path64& path);
void OffsetOpenPath(Group& group, Path64& path, EndType endType); void OffsetOpenPath(Group& group, const Path64& path);
void OffsetPoint(Group& group, Path64& path, size_t j, size_t& k); void OffsetPoint(Group& group, const Path64& path, size_t j, size_t k);
void DoGroupOffset(Group &group, double delta); void DoGroupOffset(Group &group);
void ExecuteInternal(double delta);
public: public:
ClipperOffset(double miter_limit = 2.0, explicit ClipperOffset(double miter_limit = 2.0,
double arc_tolerance = 0.0, double arc_tolerance = 0.0,
bool preserve_collinear = false, bool preserve_collinear = false,
bool reverse_solution = false) : bool reverse_solution = false) :
miter_limit_(miter_limit), arc_tolerance_(arc_tolerance), miter_limit_(miter_limit), arc_tolerance_(arc_tolerance),
preserve_collinear_(preserve_collinear), preserve_collinear_(preserve_collinear),
@ -73,13 +92,14 @@ public:
~ClipperOffset() { Clear(); }; ~ClipperOffset() { Clear(); };
int ErrorCode() const { return error_code_; };
void AddPath(const Path64& path, JoinType jt_, EndType et_); void AddPath(const Path64& path, JoinType jt_, EndType et_);
void AddPaths(const Paths64& paths, 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(); }; void Clear() { groups_.clear(); norms.clear(); };
Paths64 Execute(double delta); void Execute(double delta, Paths64& sols_64);
void Execute(double delta, PolyTree64& polytree);
void Execute(DeltaCallback64 delta_cb, Paths64& paths);
double MiterLimit() const { return miter_limit_; } double MiterLimit() const { return miter_limit_; }
void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; } void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; }
@ -88,19 +108,17 @@ public:
double ArcTolerance() const { return arc_tolerance_; } double ArcTolerance() const { return arc_tolerance_; }
void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; } void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; }
//MergeGroups: A path group is one or more paths added via the AddPath or
//AddPaths methods. By default these path groups will be offset
//independently of other groups and this may cause overlaps (intersections).
//However, when MergeGroups is enabled, any overlapping offsets will be
//merged (via a clipping union operation) to remove overlaps.
bool MergeGroups() const { return merge_groups_; }
void MergeGroups(bool merge_groups) { merge_groups_ = merge_groups; }
bool PreserveCollinear() const { return preserve_collinear_; } bool PreserveCollinear() const { return preserve_collinear_; }
void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;} void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;}
bool ReverseSolution() const { return reverse_solution_; } bool ReverseSolution() const { return reverse_solution_; }
void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;} void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;}
#ifdef USINGZ
void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; }
#endif
void SetDeltaCallback(DeltaCallback64 cb) { deltaCallback64_ = cb; }
}; };
} }

View File

@ -1,49 +1,79 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 26 October 2022 * * Date : 5 July 2024 *
* Website : http://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 * * Copyright : Angus Johnson 2010-2024 *
* Purpose : FAST rectangular clipping * * Purpose : FAST rectangular clipping *
* License : http://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
#ifndef CLIPPER_RECTCLIP_H #ifndef CLIPPER_RECTCLIP_H
#define CLIPPER_RECTCLIP_H #define CLIPPER_RECTCLIP_H
#include <cstdlib> #include "clipper2/clipper.core.h"
#include <vector> #include <queue>
#include "clipper.h"
#include "clipper.core.h"
namespace Clipper2Lib namespace Clipper2Lib
{ {
// Location: the order is important here, see StartLocsIsClockwise()
enum class Location { Left, Top, Right, Bottom, Inside }; enum class Location { Left, Top, Right, Bottom, Inside };
class RectClip { class OutPt2;
typedef std::vector<OutPt2*> OutPt2List;
class OutPt2 {
public:
Point64 pt;
size_t owner_idx = 0;
OutPt2List* edge = nullptr;
OutPt2* next = nullptr;
OutPt2* prev = nullptr;
};
//------------------------------------------------------------------------------
// RectClip64
//------------------------------------------------------------------------------
class RectClip64 {
private:
void ExecuteInternal(const Path64& path);
Path64 GetPath(OutPt2*& op);
protected: protected:
const Rect64 rect_; const Rect64 rect_;
const Point64 mp_; const Path64 rect_as_path_;
const Path64 rectPath_; const Point64 rect_mp_;
Path64 result_; Rect64 path_bounds_;
std::deque<OutPt2> op_container_;
OutPt2List results_; // each path can be broken into multiples
OutPt2List edges_[8]; // clockwise and counter-clockwise
std::vector<Location> start_locs_; std::vector<Location> start_locs_;
void CheckEdges();
void TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw);
void GetNextLocation(const Path64& path, void GetNextLocation(const Path64& path,
Location& loc, int& i, int highI); Location& loc, size_t& i, size_t highI);
OutPt2* Add(Point64 pt, bool start_new = false);
void AddCorner(Location prev, Location curr); void AddCorner(Location prev, Location curr);
void AddCorner(Location& loc, bool isClockwise); void AddCorner(Location& loc, bool isClockwise);
public: public:
RectClip(const Rect64& rect) : explicit RectClip64(const Rect64& rect) :
rect_(rect), rect_(rect),
mp_(rect.MidPoint()), rect_as_path_(rect.AsPath()),
rectPath_(rect.AsPath()) {} rect_mp_(rect.MidPoint()) {}
Path64 Execute(const Path64& path); Paths64 Execute(const Paths64& paths);
}; };
class RectClipLines : public RectClip { //------------------------------------------------------------------------------
// RectClipLines64
//------------------------------------------------------------------------------
class RectClipLines64 : public RectClip64 {
private:
void ExecuteInternal(const Path64& path);
Path64 GetPath(OutPt2*& op);
public: public:
RectClipLines(const Rect64& rect) : RectClip(rect) {}; explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {};
Paths64 Execute(const Path64& path); Paths64 Execute(const Paths64& paths);
}; };
} // Clipper2Lib namespace } // Clipper2Lib namespace

View File

@ -0,0 +1,6 @@
#ifndef CLIPPER_VERSION_H
#define CLIPPER_VERSION_H
constexpr auto CLIPPER2_VERSION = "1.5.2";
#endif // CLIPPER_VERSION_H

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +1,72 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 15 October 2022 * * Date : 22 January 2025 *
* Website : http://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2022 * * Copyright : Angus Johnson 2010-2025 *
* Purpose : Path Offset (Inflate/Shrink) * * Purpose : Path Offset (Inflate/Shrink) *
* License : http://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
#include <cmath>
#include "clipper2/clipper.h" #include "clipper2/clipper.h"
#include "clipper2/clipper.offset.h" #include "clipper2/clipper.offset.h"
namespace Clipper2Lib { namespace Clipper2Lib {
const double default_arc_tolerance = 0.25;
const double floating_point_tolerance = 1e-12; const double floating_point_tolerance = 1e-12;
// Clipper2 approximates arcs by using series of relatively short straight
//line segments. And logically, shorter line segments will produce better arc
// approximations. But very short segments can degrade performance, usually
// with little or no discernable improvement in curve quality. Very short
// segments can even detract from curve quality, due to the effects of integer
// rounding. Since there isn't an optimal number of line segments for any given
// arc radius (that perfectly balances curve approximation with performance),
// arc tolerance is user defined. Nevertheless, when the user doesn't define
// an arc tolerance (ie leaves alone the 0 default value), the calculated
// default arc tolerance (offset_radius / 500) generally produces good (smooth)
// arc approximations without producing excessively small segment lengths.
// See also: https://www.angusj.com/clipper2/Docs/Trigonometry.htm
const double arc_const = 0.002; // <-- 1/500
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Miscellaneous methods // Miscellaneous methods
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
Paths64::size_type GetLowestPolygonIdx(const Paths64& paths) std::optional<size_t> GetLowestClosedPathIdx(const Paths64& paths)
{ {
Paths64::size_type result = 0; std::optional<size_t> result;
Point64 lp = Point64(static_cast<int64_t>(0), Point64 botPt = Point64(INT64_MAX, INT64_MIN);
std::numeric_limits<int64_t>::min()); for (size_t i = 0; i < paths.size(); ++i)
{
for (Paths64::size_type i = 0 ; i < paths.size(); ++i) for (const Point64& pt : paths[i])
for (const Point64& p : paths[i]) {
{ if ((pt.y < botPt.y) ||
if (p.y < lp.y || (p.y == lp.y && p.x >= lp.x)) continue; ((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
result = i; result = i;
lp = p; botPt.x = pt.x;
} botPt.y = pt.y;
}
}
return result; return result;
} }
PointD GetUnitNormal(const Point64& pt1, const Point64& pt2) inline double Hypot(double x, double y)
{
// given that this is an internal function, and given the x and y parameters
// will always be coordinate values (or the difference between coordinate values),
// x and y should always be within INT64_MIN to INT64_MAX. Consequently,
// there should be no risk that the following computation will overflow
// see https://stackoverflow.com/a/32436148/359538
return std::sqrt(x * x + y * y);
}
static PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
{ {
double dx, dy, inverse_hypot;
if (pt1 == pt2) return PointD(0.0, 0.0); if (pt1 == pt2) return PointD(0.0, 0.0);
dx = static_cast<double>(pt2.x - pt1.x); double dx = static_cast<double>(pt2.x - pt1.x);
dy = static_cast<double>(pt2.y - pt1.y); double dy = static_cast<double>(pt2.y - pt1.y);
inverse_hypot = 1.0 / hypot(dx, dy); double inverse_hypot = 1.0 / Hypot(dx, dy);
dx *= inverse_hypot; dx *= inverse_hypot;
dy *= inverse_hypot; dy *= inverse_hypot;
return PointD(dy, -dx); return PointD(dy, -dx);
@ -53,15 +77,8 @@ inline bool AlmostZero(double value, double epsilon = 0.001)
return std::fabs(value) < epsilon; 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) inline PointD NormalizeVector(const PointD& vec)
{ {
double h = Hypot(vec.x, vec.y); double h = Hypot(vec.x, vec.y);
if (AlmostZero(h)) return PointD(0,0); if (AlmostZero(h)) return PointD(0,0);
double inverseHypot = 1 / h; double inverseHypot = 1 / h;
@ -78,14 +95,63 @@ inline bool IsClosedPath(EndType et)
return et == EndType::Polygon || et == EndType::Joined; return et == EndType::Polygon || et == EndType::Joined;
} }
inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta) static inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
{ {
#ifdef USINGZ
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
#else
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta); return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
#endif
} }
inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta) inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
{ {
#ifdef USINGZ
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
#else
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta); return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
#endif
}
inline void NegatePath(PathD& path)
{
for (PointD& pt : path)
{
pt.x = -pt.x;
pt.y = -pt.y;
#ifdef USINGZ
pt.z = pt.z;
#endif
}
}
//------------------------------------------------------------------------------
// ClipperOffset::Group methods
//------------------------------------------------------------------------------
ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type):
paths_in(_paths), join_type(_join_type), end_type(_end_type)
{
bool is_joined =
(end_type == EndType::Polygon) ||
(end_type == EndType::Joined);
for (Path64& p: paths_in)
StripDuplicates(p, is_joined);
if (end_type == EndType::Polygon)
{
lowest_path_idx = GetLowestClosedPathIdx(paths_in);
// the lowermost path must be an outer path, so if its orientation is negative,
// then flag the whole group is 'reversed' (will negate delta etc.)
// as this is much more efficient than reversing every path.
is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0;
}
else
{
lowest_path_idx = std::nullopt;
is_reversed = false;
}
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -94,28 +160,13 @@ inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_) void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_)
{ {
Paths64 paths; groups_.emplace_back(Paths64(1, path), jt_, et_);
paths.push_back(path);
AddPaths(paths, jt_, et_);
} }
void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_) void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_)
{ {
if (paths.size() == 0) return; if (paths.size() == 0) return;
groups_.push_back(Group(paths, jt_, et_)); groups_.emplace_back(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) void ClipperOffset::BuildNormals(const Path64& path)
@ -123,64 +174,55 @@ void ClipperOffset::BuildNormals(const Path64& path)
norms.clear(); norms.clear();
norms.reserve(path.size()); norms.reserve(path.size());
if (path.size() == 0) return; if (path.size() == 0) return;
Path64::const_iterator path_iter, path_last_iter = --path.cend(); Path64::const_iterator path_iter, path_stop_iter = --path.cend();
for (path_iter = path.cbegin(); path_iter != path_last_iter; ++path_iter) for (path_iter = path.cbegin(); path_iter != path_stop_iter; ++path_iter)
norms.push_back(GetUnitNormal(*path_iter,*(path_iter +1))); norms.emplace_back(GetUnitNormal(*path_iter,*(path_iter +1)));
norms.push_back(GetUnitNormal(*path_last_iter, *(path.cbegin()))); norms.emplace_back(GetUnitNormal(*path_stop_iter, *(path.cbegin())));
} }
inline PointD TranslatePoint(const PointD& pt, double dx, double dy) void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k)
{ {
return PointD(pt.x + dx, pt.y + dy); PointD pt1, pt2;
} if (j == k)
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 abs_delta = std::abs(group_delta_);
#ifdef USINGZ
double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x); pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y, path[j].z);
double b2 = pt2a.y - m2 * pt2a.x; pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y, path[j].z);
return PointD(pt1a.x, m2 * pt1a.x + b2); #else
} pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y);
else if (pt2a.x == pt2b.x) //vertical pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y);
{ #endif
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 else
{ {
double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x); #ifdef USINGZ
double b1 = pt1a.y - m1 * pt1a.x; pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y, path[j].z);
double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x); pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y, path[j].z);
double b2 = pt2a.y - m2 * pt2a.x; #else
if (m1 == m2) return PointD(0, 0); pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y);
double x = (b2 - b1) / (m1 - m2); pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y);
return PointD(x, m1 * x + b1); #endif
} }
path_out.emplace_back(pt1);
path_out.emplace_back(pt2);
} }
void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k) void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
{ {
PointD vec; PointD vec;
if (j == k) if (j == k)
vec = PointD(norms[0].y, -norms[0].x); vec = PointD(norms[j].y, -norms[j].x);
else else
vec = GetAvgUnitVector( vec = GetAvgUnitVector(
PointD(-norms[k].y, norms[k].x), PointD(-norms[k].y, norms[k].x),
PointD(norms[j].y, -norms[j].x)); PointD(norms[j].y, -norms[j].x));
double abs_delta = std::abs(group_delta_);
// now offset the original vertex delta units along unit vector // now offset the original vertex delta units along unit vector
PointD ptQ = PointD(path[j]); PointD ptQ = PointD(path[j]);
ptQ = TranslatePoint(ptQ, abs_group_delta_ * vec.x, abs_group_delta_ * vec.y); ptQ = TranslatePoint(ptQ, abs_delta * vec.x, abs_delta * vec.y);
// get perpendicular vertices // get perpendicular vertices
PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x); PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x);
PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x); PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x);
@ -189,51 +231,77 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t
if (j == k) if (j == k)
{ {
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_); PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
PointD pt = IntersectPoint(pt1, pt2, pt3, pt4); PointD pt = ptQ;
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
//get the second intersect point through reflecion //get the second intersect point through reflecion
group.path_.push_back(Point64(ReflectPoint(pt, ptQ))); path_out.emplace_back(ReflectPoint(pt, ptQ));
group.path_.push_back(Point64(pt)); path_out.emplace_back(pt);
} }
else else
{ {
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_); PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
PointD pt = IntersectPoint(pt1, pt2, pt3, pt4); PointD pt = ptQ;
group.path_.push_back(Point64(pt)); GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
path_out.emplace_back(pt);
//get the second intersect point through reflecion //get the second intersect point through reflecion
group.path_.push_back(Point64(ReflectPoint(pt, ptQ))); path_out.emplace_back(ReflectPoint(pt, ptQ));
} }
} }
void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a) void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a)
{ {
double q = group_delta_ / (cos_a + 1); double q = group_delta_ / (cos_a + 1);
group.path_.push_back(Point64( #ifdef USINGZ
path_out.emplace_back(
path[j].x + (norms[k].x + norms[j].x) * q, path[j].x + (norms[k].x + norms[j].x) * q,
path[j].y + (norms[k].y + norms[j].y) * q)); path[j].y + (norms[k].y + norms[j].y) * q,
path[j].z);
#else
path_out.emplace_back(
path[j].x + (norms[k].x + norms[j].x) * q,
path[j].y + (norms[k].y + norms[j].y) * q);
#endif
} }
void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle) void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle)
{ {
//even though angle may be negative this is a convex join if (deltaCallback64_) {
Point64 pt = path[j]; // when deltaCallback64_ is assigned, group_delta_ won't be constant,
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // so we'll need to do the following calculations for *every* vertex.
double step_sin = std::sin(angle / steps); double abs_delta = std::fabs(group_delta_);
double step_cos = std::cos(angle / steps); double arcTol = (arc_tolerance_ > floating_point_tolerance ?
std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const);
PointD pt2 = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_); double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
if (j == k) pt2.Negate(); step_sin_ = std::sin(2 * PI / steps_per_360);
step_cos_ = std::cos(2 * PI / steps_per_360);
group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y)); if (group_delta_ < 0.0) step_sin_ = -step_sin_;
for (int i = 0; i < steps; i++) steps_per_rad_ = steps_per_360 / (2 * PI);
{
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_));
Point64 pt = path[j];
PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
if (j == k) offsetVec.Negate();
#ifdef USINGZ
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z);
#else
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
#endif
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
for (int i = 1; i < steps; ++i) // ie 1 less than steps
{
offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
#ifdef USINGZ
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z);
#else
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
#endif
}
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_));
} }
void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size_t k)
{ {
// Let A = change in angle where edges join // Let A = change in angle where edges join
// A == 0: ie no change in angle (flat join) // A == 0: ie no change in angle (flat join)
@ -241,245 +309,346 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k)
// sin(A) < 0: right turning // sin(A) < 0: right turning
// cos(A) < 0: change in angle is more than 90 degree // cos(A) < 0: change in angle is more than 90 degree
if (path[j] == path[k]) { k = j; return; } if (path[j] == path[k]) return;
double sin_a = CrossProduct(norms[j], norms[k]); double sin_a = CrossProduct(norms[j], norms[k]);
double cos_a = DotProduct(norms[j], norms[k]); double cos_a = DotProduct(norms[j], norms[k]);
if (sin_a > 1.0) sin_a = 1.0; if (sin_a > 1.0) sin_a = 1.0;
else if (sin_a < -1.0) sin_a = -1.0; else if (sin_a < -1.0) sin_a = -1.0;
bool almostNoAngle = AlmostZero(sin_a) && cos_a > 0; if (deltaCallback64_) {
// when there's almost no angle of deviation or it's concave group_delta_ = deltaCallback64_(path, norms, j, k);
if (almostNoAngle || (sin_a * group_delta_ < 0)) if (group.is_reversed) group_delta_ = -group_delta_;
{
Point64 p1 = Point64(
path[j].x + norms[k].x * group_delta_,
path[j].y + norms[k].y * group_delta_);
Point64 p2 = Point64(
path[j].x + norms[j].x * group_delta_,
path[j].y + norms[j].y * group_delta_);
group.path_.push_back(p1);
if (p1 != p2)
{
// when concave add an extra vertex to ensure neat clipping
if (!almostNoAngle) group.path_.push_back(path[j]);
group.path_.push_back(p2);
}
} }
else // it's convex if (std::fabs(group_delta_) <= floating_point_tolerance)
{ {
if (join_type_ == JoinType::Round) path_out.emplace_back(path[j]);
DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); return;
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;
if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
{
// is concave
// by far the simplest way to construct concave joins, especially those joining very
// short segments, is to insert 3 points that produce negative regions. These regions
// will be removed later by the finishing union operation. This is also the best way
// to ensure that path reversals (ie over-shrunk paths) are removed.
#ifdef USINGZ
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_), path[j].z);
path_out.emplace_back(path[j]); // (#405, #873, #916)
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_), path[j].z);
#else
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_));
path_out.emplace_back(path[j]); // (#405, #873, #916)
path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_));
#endif
}
else if (cos_a > 0.999 && join_type_ != JoinType::Round)
{
// almost straight - less than 2.5 degree (#424, #482, #526 & #724)
DoMiter(path, j, k, cos_a);
}
else if (join_type_ == JoinType::Miter)
{
// miter unless the angle is sufficiently acute to exceed ML
if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a);
else DoSquare(path, j, k);
}
else if (join_type_ == JoinType::Round)
DoRound(path, j, k, std::atan2(sin_a, cos_a));
else if ( join_type_ == JoinType::Bevel)
DoBevel(path, j, k);
else
DoSquare(path, j, k);
} }
void ClipperOffset::OffsetPolygon(Group& group, Path64& path) void ClipperOffset::OffsetPolygon(Group& group, const Path64& path)
{ {
group.path_.clear(); path_out.clear();
for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i) for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j)
OffsetPoint(group, path, i, j); OffsetPoint(group, path, j, k);
group.paths_out_.push_back(group.path_); solution->emplace_back(path_out);
} }
void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path) void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
{ {
OffsetPolygon(group, path); OffsetPolygon(group, path);
std::reverse(path.begin(), path.end()); Path64 reverse_path(path);
BuildNormals(path); std::reverse(reverse_path.begin(), reverse_path.end());
OffsetPolygon(group, path);
//rebuild normals
std::reverse(norms.begin(), norms.end());
norms.emplace_back(norms[0]);
norms.erase(norms.begin());
NegatePath(norms);
OffsetPolygon(group, reverse_path);
} }
void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type) void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
{ {
group.path_.clear();
// do the line start cap // do the line start cap
switch (end_type) if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0);
if (std::fabs(group_delta_) <= floating_point_tolerance)
path_out.emplace_back(path[0]);
else
{ {
case EndType::Butt: switch (end_type_)
group.path_.push_back(Point64( {
path[0].x - norms[0].x * group_delta_, case EndType::Butt:
path[0].y - norms[0].y * group_delta_)); DoBevel(path, 0, 0);
group.path_.push_back(GetPerpendic(path[0], norms[0], group_delta_)); break;
break; case EndType::Round:
case EndType::Round: DoRound(path, 0, 0, PI);
DoRound(group, path, 0,0, PI); break;
break; default:
default: DoSquare(path, 0, 0);
DoSquare(group, path, 0, 0); break;
break; }
} }
size_t highI = path.size() - 1; size_t highI = path.size() - 1;
// offset the left side going forward // offset the left side going forward
for (Path64::size_type i = 1, k = 0; i < highI; ++i) for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j)
OffsetPoint(group, path, i, k); OffsetPoint(group, path, j, k);
// reverse normals // reverse normals
for (size_t i = highI; i > 0; --i) for (size_t i = highI; i > 0; --i)
norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y); norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
norms[0] = norms[highI]; norms[0] = norms[highI];
// do the line end cap // do the line end cap
switch (end_type) if (deltaCallback64_)
group_delta_ = deltaCallback64_(path, norms, highI, highI);
if (std::fabs(group_delta_) <= floating_point_tolerance)
path_out.emplace_back(path[highI]);
else
{ {
case EndType::Butt: switch (end_type_)
group.path_.push_back(Point64( {
path[highI].x - norms[highI].x * group_delta_, case EndType::Butt:
path[highI].y - norms[highI].y * group_delta_)); DoBevel(path, highI, highI);
group.path_.push_back(GetPerpendic(path[highI], norms[highI], group_delta_)); break;
break; case EndType::Round:
case EndType::Round: DoRound(path, highI, highI, PI);
DoRound(group, path, highI, highI, PI); break;
break; default:
default: DoSquare(path, highI, highI);
DoSquare(group, path, highI, highI); break;
break; }
} }
for (size_t i = highI, k = 0; i > 0; --i) for (size_t j = highI -1, k = highI; j > 0; k = j, --j)
OffsetPoint(group, path, i, k); OffsetPoint(group, path, j, k);
group.paths_out_.push_back(group.path_); solution->emplace_back(path_out);
} }
void ClipperOffset::DoGroupOffset(Group& group, double delta) void ClipperOffset::DoGroupOffset(Group& group)
{ {
if (group.end_type_ != EndType::Polygon) delta = std::abs(delta) * 0.5; if (group.end_type == EndType::Polygon)
bool isClosedPaths = IsClosedPath(group.end_type_);
if (isClosedPaths)
{ {
//the lowermost polygon must be an outer polygon. So we can use that as the // a straight path (2 points) can now also be 'polygon' offset
//designated orientation for outer polygons (needed for tidy-up clipping) // where the ends will be treated as (180 deg.) joins
Paths64::size_type lowestIdx = GetLowestPolygonIdx(group.paths_in_); if (!group.lowest_path_idx.has_value()) delta_ = std::abs(delta_);
// nb: don't use the default orientation here ... group_delta_ = (group.is_reversed) ? -delta_ : delta_;
double area = Area(group.paths_in_[lowestIdx]); }
if (area == 0) return;
group.is_reversed_ = (area < 0);
if (group.is_reversed_) delta = -delta;
}
else else
group.is_reversed_ = false; group_delta_ = std::abs(delta_);// *0.5;
group_delta_ = delta; double abs_delta = std::fabs(group_delta_);
abs_group_delta_ = std::abs(group_delta_); join_type_ = group.join_type;
join_type_ = group.join_type_; end_type_ = group.end_type;
double arcTol = (arc_tolerance_ > floating_point_tolerance ? arc_tolerance_ if (group.join_type == JoinType::Round || group.end_type == EndType::Round)
: 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); // calculate the number of steps required to approximate a circle
// (see https://www.angusj.com/clipper2/Docs/Trigonometry.htm)
// arcTol - when arc_tolerance_ is undefined (0) then curve imprecision
// will be relative to the size of the offset (delta). Obviously very
//large offsets will almost always require much less precision.
double arcTol = (arc_tolerance_ > floating_point_tolerance) ?
std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const;
double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
step_sin_ = std::sin(2 * PI / steps_per_360);
step_cos_ = std::cos(2 * PI / steps_per_360);
if (group_delta_ < 0.0) step_sin_ = -step_sin_;
steps_per_rad_ = steps_per_360 / (2 * PI);
} }
bool is_closed_path = IsClosedPath(group.end_type_); //double min_area = PI * Sqr(group_delta_);
Paths64::const_iterator path_iter; Paths64::const_iterator path_in_it = group.paths_in.cbegin();
for(path_iter = group.paths_in_.cbegin(); path_iter != group.paths_in_.cend(); ++path_iter) for ( ; path_in_it != group.paths_in.cend(); ++path_in_it)
{ {
Path64 path = StripDuplicates(*path_iter, is_closed_path); Path64::size_type pathLen = path_in_it->size();
Path64::size_type cnt = path.size(); path_out.clear();
if (cnt == 0) continue;
if (cnt == 1) // single point - only valid with open paths if (pathLen == 1) // single point
{ {
group.path_ = Path64(); if (deltaCallback64_)
//single vertex so build a circle or square ...
if (group.join_type_ == JoinType::Round)
{ {
double radius = abs_group_delta_; group_delta_ = deltaCallback64_(*path_in_it, norms, 0, 0);
group.path_ = Ellipse(path[0], radius, radius); if (group.is_reversed) group_delta_ = -group_delta_;
abs_delta = std::fabs(group_delta_);
}
if (group_delta_ < 1) continue;
const Point64& pt = (*path_in_it)[0];
//single vertex so build a circle or square ...
if (group.join_type == JoinType::Round)
{
double radius = abs_delta;
size_t steps = steps_per_rad_ > 0 ? static_cast<size_t>(std::ceil(steps_per_rad_ * 2 * PI)) : 0; //#617
path_out = Ellipse(pt, radius, radius, steps);
#ifdef USINGZ
for (auto& p : path_out) p.z = pt.z;
#endif
} }
else else
{ {
int d = (int)std::ceil(abs_group_delta_); int d = (int)std::ceil(abs_delta);
Rect64 r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d); Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d);
group.path_ = r.AsPath(); path_out = r.AsPath();
#ifdef USINGZ
for (auto& p : path_out) p.z = pt.z;
#endif
} }
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_);
}
}
if (!merge_groups_) solution->emplace_back(path_out);
{ continue;
//clean up self-intersections ... } // end of offsetting a single point
Clipper64 c;
c.PreserveCollinear = false;
//the solution should retain the orientation of the input
c.ReverseSolution = reverse_solution_ != group.is_reversed_;
c.AddSubject(group.paths_out_);
if (group.is_reversed_)
c.Execute(ClipType::Union, FillRule::Negative, group.paths_out_);
else
c.Execute(ClipType::Union, FillRule::Positive, group.paths_out_);
}
solution.reserve(solution.size() + group.paths_out_.size()); if ((pathLen == 2) && (group.end_type == EndType::Joined))
copy(group.paths_out_.begin(), group.paths_out_.end(), back_inserter(solution)); end_type_ = (group.join_type == JoinType::Round) ?
group.paths_out_.clear(); EndType::Round :
EndType::Square;
BuildNormals(*path_in_it);
if (end_type_ == EndType::Polygon) OffsetPolygon(group, *path_in_it);
else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, *path_in_it);
else OffsetOpenPath(group, *path_in_it);
}
} }
Paths64 ClipperOffset::Execute(double delta) #ifdef USINGZ
void ClipperOffset::ZCB(const Point64& bot1, const Point64& top1,
const Point64& bot2, const Point64& top2, Point64& ip)
{ {
solution.clear(); if (bot1.z && ((bot1.z == bot2.z) || (bot1.z == top2.z))) ip.z = bot1.z;
if (std::abs(delta) < default_arc_tolerance) else if (bot2.z && (bot2.z == top1.z)) ip.z = bot2.z;
{ else if (top1.z && (top1.z == top2.z)) ip.z = top1.z;
for (const Group& group : groups_) else if (zCallback64_) zCallback64_(bot1, top1, bot2, top2, ip);
}
#endif
size_t ClipperOffset::CalcSolutionCapacity()
{
size_t result = 0;
for (const Group& g : groups_)
result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size();
return result;
}
bool ClipperOffset::CheckReverseOrientation()
{
// nb: this assumes there's consistency in orientation between groups
bool is_reversed_orientation = false;
for (const Group& g : groups_)
if (g.end_type == EndType::Polygon)
{ {
solution.reserve(solution.size() + group.paths_in_.size()); is_reversed_orientation = g.is_reversed;
copy(group.paths_in_.begin(), group.paths_in_.end(), back_inserter(solution)); break;
}
return is_reversed_orientation;
}
void ClipperOffset::ExecuteInternal(double delta)
{
error_code_ = 0;
if (groups_.size() == 0) return;
solution->reserve(CalcSolutionCapacity());
if (std::abs(delta) < 0.5) // ie: offset is insignificant
{
Paths64::size_type sol_size = 0;
for (const Group& group : groups_) sol_size += group.paths_in.size();
solution->reserve(sol_size);
for (const Group& group : groups_)
copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(*solution));
}
else
{
temp_lim_ = (miter_limit_ <= 1) ?
2.0 :
2.0 / (miter_limit_ * miter_limit_);
delta_ = delta;
std::vector<Group>::iterator git;
for (git = groups_.begin(); git != groups_.end(); ++git)
{
DoGroupOffset(*git);
if (!error_code_) continue; // all OK
solution->clear();
} }
return solution;
} }
temp_lim_ = (miter_limit_ <= 1) ? if (!solution->size()) return;
2.0 :
2.0 / (miter_limit_ * miter_limit_);
std::vector<Group>::iterator groups_iter; bool paths_reversed = CheckReverseOrientation();
for (groups_iter = groups_.begin(); //clean up self-intersections ...
groups_iter != groups_.end(); ++groups_iter) Clipper64 c;
c.PreserveCollinear(false);
//the solution should retain the orientation of the input
c.ReverseSolution(reverse_solution_ != paths_reversed);
#ifdef USINGZ
auto fp = std::bind(&ClipperOffset::ZCB, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4, std::placeholders::_5);
c.SetZCallback(fp);
#endif
c.AddSubject(*solution);
if (solution_tree)
{ {
DoGroupOffset(*groups_iter, delta); if (paths_reversed)
} c.Execute(ClipType::Union, FillRule::Negative, *solution_tree);
if (merge_groups_ && groups_.size() > 0)
{
//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 else
c.Execute(ClipType::Union, FillRule::Positive, solution); c.Execute(ClipType::Union, FillRule::Positive, *solution_tree);
} }
return solution; else
{
if (paths_reversed)
c.Execute(ClipType::Union, FillRule::Negative, *solution);
else
c.Execute(ClipType::Union, FillRule::Positive, *solution);
}
}
void ClipperOffset::Execute(double delta, Paths64& paths64)
{
paths64.clear();
solution = &paths64;
solution_tree = nullptr;
ExecuteInternal(delta);
}
void ClipperOffset::Execute(double delta, PolyTree64& polytree)
{
polytree.Clear();
solution_tree = &polytree;
solution = new Paths64();
ExecuteInternal(delta);
delete solution;
solution = nullptr;
}
void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths)
{
deltaCallback64_ = delta_cb;
Execute(1.0, paths);
} }
} // namespace } // namespace

File diff suppressed because it is too large Load Diff

View File

@ -57,7 +57,7 @@ static ExPolygons PolyTreeToExPolygons(Clipper2Lib::PolyTree64 &&polytree)
for (int i = 0; i < polynode.Count(); ++i) { for (int i = 0; i < polynode.Count(); ++i) {
(*expolygons)[cnt].holes[i].points = Path64ToPoints(polynode[i]->Polygon()); (*expolygons)[cnt].holes[i].points = Path64ToPoints(polynode[i]->Polygon());
// Add outer polygons contained by (nested within) holes. // Add outer polygons contained by (nested within) holes.
for (int j = 0; j < polynode[i]->Count(); ++j) PolyTreeToExPolygonsRecursive(std::move(*polynode[i]->Childs(j)), expolygons); for (int j = 0; j < polynode[i]->Count(); ++j) PolyTreeToExPolygonsRecursive(std::move(*polynode[i]->Child(j)), expolygons);
} }
} }
@ -65,7 +65,7 @@ static ExPolygons PolyTreeToExPolygons(Clipper2Lib::PolyTree64 &&polytree)
{ {
size_t cnt = 1; size_t cnt = 1;
for (size_t i = 0; i < polynode.Count(); ++i) { for (size_t i = 0; i < polynode.Count(); ++i) {
for (size_t j = 0; j < polynode.Childs(i)->Count(); ++j) cnt += PolyTreeCountExPolygons(*polynode.Childs(i)->Childs(j)); for (size_t j = 0; j < polynode.Child(i)->Count(); ++j) cnt += PolyTreeCountExPolygons(*polynode.Child(i)->Child(j));
} }
return cnt; return cnt;
} }