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:
parent
21d1159cd5
commit
c486b42e9c
File diff suppressed because it is too large
Load Diff
|
@ -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 {
|
||||||
|
@ -140,9 +142,22 @@ namespace Clipper2Lib {
|
||||||
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
|
||||||
|
@ -153,11 +168,30 @@ namespace Clipper2Lib {
|
||||||
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,46 +315,54 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,74 +375,69 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
@ -1,19 +1,16 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* 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
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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,47 +27,62 @@ 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) :
|
||||||
|
@ -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; }
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
@ -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& p : paths[i])
|
|
||||||
{
|
{
|
||||||
if (p.y < lp.y || (p.y == lp.y && p.x >= lp.x)) continue;
|
for (const Point64& pt : paths[i])
|
||||||
|
{
|
||||||
|
if ((pt.y < botPt.y) ||
|
||||||
|
((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));
|
double abs_delta = std::abs(group_delta_);
|
||||||
}
|
#ifdef USINGZ
|
||||||
|
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y, path[j].z);
|
||||||
PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b,
|
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y, path[j].z);
|
||||||
const PointD& pt2a, const PointD& pt2b)
|
#else
|
||||||
{
|
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y);
|
||||||
if (pt1a.x == pt1b.x) //vertical
|
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y);
|
||||||
{
|
#endif
|
||||||
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
|
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_) {
|
||||||
|
// when deltaCallback64_ is assigned, group_delta_ won't be constant,
|
||||||
|
// so we'll need to do the following calculations for *every* vertex.
|
||||||
|
double abs_delta = std::fabs(group_delta_);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
Point64 pt = path[j];
|
Point64 pt = path[j];
|
||||||
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle)));
|
PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
|
||||||
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) offsetVec.Negate();
|
||||||
if (j == k) pt2.Negate();
|
#ifdef USINGZ
|
||||||
|
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z);
|
||||||
group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y));
|
#else
|
||||||
for (int i = 0; i < steps; i++)
|
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
|
||||||
{
|
{
|
||||||
pt2 = PointD(pt2.x * step_cos - step_sin * pt2.y,
|
offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
|
||||||
pt2.x * step_sin + pt2.y * step_cos);
|
offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
|
||||||
group.path_.push_back(Point64(pt.x + pt2.x, pt.y + pt2.y));
|
#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
|
||||||
}
|
}
|
||||||
group.path_.push_back(GetPerpendic(path[j], norms[j], group_delta_));
|
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,93 +309,109 @@ 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);
|
|
||||||
}
|
}
|
||||||
}
|
if (std::fabs(group_delta_) <= floating_point_tolerance)
|
||||||
else // it's convex
|
|
||||||
{
|
{
|
||||||
if (join_type_ == JoinType::Round)
|
path_out.emplace_back(path[j]);
|
||||||
DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
else if (join_type_ == JoinType::Miter)
|
||||||
{
|
{
|
||||||
// miter unless the angle is so acute the miter would exceeds ML
|
// miter unless the angle is sufficiently acute to exceed ML
|
||||||
if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
|
if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a);
|
||||||
else DoSquare(group, path, j, k);
|
else DoSquare(path, j, k);
|
||||||
}
|
}
|
||||||
// don't bother squaring angles that deviate < ~20 degrees because
|
else if (join_type_ == JoinType::Round)
|
||||||
// squaring will be indistinguishable from mitering and just be a lot slower
|
DoRound(path, j, k, std::atan2(sin_a, cos_a));
|
||||||
else if (cos_a > 0.9)
|
else if ( join_type_ == JoinType::Bevel)
|
||||||
DoMiter(group, path, j, k, cos_a);
|
DoBevel(path, j, k);
|
||||||
else
|
else
|
||||||
DoSquare(group, path, j, k);
|
DoSquare(path, j, k);
|
||||||
}
|
|
||||||
k = j;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
{
|
||||||
|
switch (end_type_)
|
||||||
{
|
{
|
||||||
case EndType::Butt:
|
case EndType::Butt:
|
||||||
group.path_.push_back(Point64(
|
DoBevel(path, 0, 0);
|
||||||
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;
|
break;
|
||||||
case EndType::Round:
|
case EndType::Round:
|
||||||
DoRound(group, path, 0,0, PI);
|
DoRound(path, 0, 0, PI);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
DoSquare(group, path, 0, 0);
|
DoSquare(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)
|
||||||
|
@ -335,151 +419,236 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path, EndType end_type)
|
||||||
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
|
||||||
|
{
|
||||||
|
switch (end_type_)
|
||||||
{
|
{
|
||||||
case EndType::Butt:
|
case EndType::Butt:
|
||||||
group.path_.push_back(Point64(
|
DoBevel(path, highI, highI);
|
||||||
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;
|
break;
|
||||||
case EndType::Round:
|
case EndType::Round:
|
||||||
DoRound(group, path, highI, highI, PI);
|
DoRound(path, highI, highI, PI);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
DoSquare(group, path, highI, highI);
|
DoSquare(path, highI, highI);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = highI, k = 0; i > 0; --i)
|
|
||||||
OffsetPoint(group, path, i, k);
|
|
||||||
group.paths_out_.push_back(group.path_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClipperOffset::DoGroupOffset(Group& group, double delta)
|
for (size_t j = highI -1, k = highI; j > 0; k = j, --j)
|
||||||
{
|
OffsetPoint(group, path, j, k);
|
||||||
if (group.end_type_ != EndType::Polygon) delta = std::abs(delta) * 0.5;
|
solution->emplace_back(path_out);
|
||||||
bool isClosedPaths = IsClosedPath(group.end_type_);
|
}
|
||||||
|
|
||||||
if (isClosedPaths)
|
void ClipperOffset::DoGroupOffset(Group& group)
|
||||||
{
|
{
|
||||||
//the lowermost polygon must be an outer polygon. So we can use that as the
|
if (group.end_type == EndType::Polygon)
|
||||||
//designated orientation for outer polygons (needed for tidy-up clipping)
|
{
|
||||||
Paths64::size_type lowestIdx = GetLowestPolygonIdx(group.paths_in_);
|
// a straight path (2 points) can now also be 'polygon' offset
|
||||||
// nb: don't use the default orientation here ...
|
// where the ends will be treated as (180 deg.) joins
|
||||||
double area = Area(group.paths_in_[lowestIdx]);
|
if (!group.lowest_path_idx.has_value()) delta_ = std::abs(delta_);
|
||||||
if (area == 0) return;
|
group_delta_ = (group.is_reversed) ? -delta_ : delta_;
|
||||||
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_)
|
||||||
|
{
|
||||||
|
group_delta_ = deltaCallback64_(*path_in_it, norms, 0, 0);
|
||||||
|
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 ...
|
//single vertex so build a circle or square ...
|
||||||
if (group.join_type_ == JoinType::Round)
|
if (group.join_type == JoinType::Round)
|
||||||
{
|
{
|
||||||
double radius = abs_group_delta_;
|
double radius = abs_delta;
|
||||||
group.path_ = Ellipse(path[0], radius, radius);
|
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_);
|
|
||||||
}
|
solution->emplace_back(path_out);
|
||||||
else
|
continue;
|
||||||
{
|
} // end of offsetting a single point
|
||||||
BuildNormals(path);
|
|
||||||
if (group.end_type_ == EndType::Polygon) OffsetPolygon(group, path);
|
if ((pathLen == 2) && (group.end_type == EndType::Joined))
|
||||||
else if (group.end_type_ == EndType::Joined) OffsetOpenJoined(group, path);
|
end_type_ = (group.join_type == JoinType::Round) ?
|
||||||
else OffsetOpenPath(group, path, group.end_type_);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!merge_groups_)
|
#ifdef USINGZ
|
||||||
|
void ClipperOffset::ZCB(const Point64& bot1, const Point64& top1,
|
||||||
|
const Point64& bot2, const Point64& top2, Point64& ip)
|
||||||
{
|
{
|
||||||
//clean up self-intersections ...
|
if (bot1.z && ((bot1.z == bot2.z) || (bot1.z == top2.z))) ip.z = bot1.z;
|
||||||
Clipper64 c;
|
else if (bot2.z && (bot2.z == top1.z)) ip.z = bot2.z;
|
||||||
c.PreserveCollinear = false;
|
else if (top1.z && (top1.z == top2.z)) ip.z = top1.z;
|
||||||
//the solution should retain the orientation of the input
|
else if (zCallback64_) zCallback64_(bot1, top1, bot2, top2, ip);
|
||||||
c.ReverseSolution = reverse_solution_ != group.is_reversed_;
|
}
|
||||||
c.AddSubject(group.paths_out_);
|
#endif
|
||||||
if (group.is_reversed_)
|
|
||||||
c.Execute(ClipType::Union, FillRule::Negative, group.paths_out_);
|
size_t ClipperOffset::CalcSolutionCapacity()
|
||||||
else
|
{
|
||||||
c.Execute(ClipType::Union, FillRule::Positive, group.paths_out_);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
solution.reserve(solution.size() + group.paths_out_.size());
|
bool ClipperOffset::CheckReverseOrientation()
|
||||||
copy(group.paths_out_.begin(), group.paths_out_.end(), back_inserter(solution));
|
{
|
||||||
group.paths_out_.clear();
|
// 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)
|
||||||
|
{
|
||||||
|
is_reversed_orientation = g.is_reversed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return is_reversed_orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
Paths64 ClipperOffset::Execute(double delta)
|
void ClipperOffset::ExecuteInternal(double delta)
|
||||||
{
|
{
|
||||||
solution.clear();
|
error_code_ = 0;
|
||||||
if (std::abs(delta) < default_arc_tolerance)
|
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_)
|
for (const Group& group : groups_)
|
||||||
|
copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(*solution));
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
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) ?
|
temp_lim_ = (miter_limit_ <= 1) ?
|
||||||
2.0 :
|
2.0 :
|
||||||
2.0 / (miter_limit_ * miter_limit_);
|
2.0 / (miter_limit_ * miter_limit_);
|
||||||
|
|
||||||
std::vector<Group>::iterator groups_iter;
|
delta_ = delta;
|
||||||
for (groups_iter = groups_.begin();
|
std::vector<Group>::iterator git;
|
||||||
groups_iter != groups_.end(); ++groups_iter)
|
for (git = groups_.begin(); git != groups_.end(); ++git)
|
||||||
{
|
{
|
||||||
DoGroupOffset(*groups_iter, delta);
|
DoGroupOffset(*git);
|
||||||
|
if (!error_code_) continue; // all OK
|
||||||
|
solution->clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (merge_groups_ && groups_.size() > 0)
|
if (!solution->size()) return;
|
||||||
{
|
|
||||||
|
bool paths_reversed = CheckReverseOrientation();
|
||||||
//clean up self-intersections ...
|
//clean up self-intersections ...
|
||||||
Clipper64 c;
|
Clipper64 c;
|
||||||
c.PreserveCollinear = false;
|
c.PreserveCollinear(false);
|
||||||
//the solution should retain the orientation of the input
|
//the solution should retain the orientation of the input
|
||||||
c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed_;
|
c.ReverseSolution(reverse_solution_ != paths_reversed);
|
||||||
|
#ifdef USINGZ
|
||||||
c.AddSubject(solution);
|
auto fp = std::bind(&ClipperOffset::ZCB, this, std::placeholders::_1,
|
||||||
if (groups_[0].is_reversed_)
|
std::placeholders::_2, std::placeholders::_3,
|
||||||
c.Execute(ClipType::Union, FillRule::Negative, solution);
|
std::placeholders::_4, std::placeholders::_5);
|
||||||
|
c.SetZCallback(fp);
|
||||||
|
#endif
|
||||||
|
c.AddSubject(*solution);
|
||||||
|
if (solution_tree)
|
||||||
|
{
|
||||||
|
if (paths_reversed)
|
||||||
|
c.Execute(ClipType::Union, FillRule::Negative, *solution_tree);
|
||||||
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
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue