Update Clipper2 to 9d946d7

Fixed a number of smaller issues as well as overlapping segment
cleaning.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/14682
This commit is contained in:
Seth Hillbrand 2023-06-01 11:37:20 -07:00
parent d854e2bfe8
commit fe62a3f985
9 changed files with 467 additions and 317 deletions

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 22 April 2023 * * Date : 15 May 2023 *
* Website : http://www.angusj.com * * Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 * * Copyright : Angus Johnson 2010-2023 *
* Purpose : This is the main polygon clipping module * * Purpose : This is the main polygon clipping module *
@ -13,6 +13,7 @@
constexpr auto CLIPPER2_VERSION = "1.2.2"; constexpr auto CLIPPER2_VERSION = "1.2.2";
#include <cstdlib> #include <cstdlib>
#include <stdint.h>
#include <iostream> #include <iostream>
#include <queue> #include <queue>
#include <vector> #include <vector>
@ -25,7 +26,6 @@ constexpr auto CLIPPER2_VERSION = "1.2.2";
#ifdef None #ifdef None
#undef None #undef None
#endif #endif
namespace Clipper2Lib { namespace Clipper2Lib {
struct Scanline; struct Scanline;
@ -183,6 +183,20 @@ namespace Clipper2Lib {
typedef std::vector<LocalMinima_ptr> LocalMinimaList; typedef std::vector<LocalMinima_ptr> LocalMinimaList;
typedef std::vector<IntersectNode> IntersectNodeList; 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 {
@ -260,7 +274,7 @@ namespace Clipper2Lib {
bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees); bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
void CleanCollinear(OutRec* outrec); void CleanCollinear(OutRec* outrec);
bool CheckBounds(OutRec* outrec); bool CheckBounds(OutRec* outrec);
bool CheckSplitOwner(OutRec* outrec); bool CheckSplitOwner(OutRec* outrec, OutRecList* splits);
void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath); void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath);
#ifdef USINGZ #ifdef USINGZ
ZCallback64 zCallback_ = nullptr; ZCallback64 zCallback_ = nullptr;
@ -275,6 +289,7 @@ namespace Clipper2Lib {
bool PreserveCollinear = true; bool PreserveCollinear = true;
bool ReverseSolution = false; bool ReverseSolution = false;
void Clear(); void Clear();
void AddReuseableData(const ReuseableDataContainer64& reuseable_data);
#ifdef USINGZ #ifdef USINGZ
int64_t DefaultZ = 0; int64_t DefaultZ = 0;
#endif #endif
@ -336,7 +351,7 @@ namespace Clipper2Lib {
const PolyPath64* operator [] (size_t index) const const PolyPath64* operator [] (size_t index) const
{ {
return childs_[index].get(); return childs_[index].get(); //std::unique_ptr
} }
const PolyPath64* Child(size_t index) const const PolyPath64* Child(size_t index) const
@ -379,12 +394,12 @@ namespace Clipper2Lib {
class PolyPathD : public PolyPath { class PolyPathD : public PolyPath {
private: private:
PolyPathDList childs_; PolyPathDList childs_;
double inv_scale_; double scale_;
PathD polygon_; PathD polygon_;
public: public:
explicit 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() { ~PolyPathD() {
@ -404,14 +419,14 @@ namespace Clipper2Lib {
PolyPathDList::const_iterator begin() const { return childs_.cbegin(); } PolyPathDList::const_iterator begin() const { return childs_.cbegin(); }
PolyPathDList::const_iterator end() const { return childs_.cend(); } PolyPathDList::const_iterator end() const { return childs_.cend(); }
void SetInvScale(double value) { inv_scale_ = value; } void SetScale(double value) { scale_ = value; }
double InvScale() { return inv_scale_; } double Scale() { return scale_; }
PolyPathD* AddChild(const Path64& path) override PolyPathD* AddChild(const Path64& path) override
{ {
int error_code = 0; int error_code = 0;
auto p = std::make_unique<PolyPathD>(this); auto p = std::make_unique<PolyPathD>(this);
PolyPathD* result = childs_.emplace_back(std::move(p)).get(); PolyPathD* result = childs_.emplace_back(std::move(p)).get();
result->polygon_ = ScalePath<double, int64_t>(path, inv_scale_, error_code); result->polygon_ = ScalePath<double, int64_t>(path, scale_, error_code);
return result; return result;
} }
@ -599,7 +614,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);
} }

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 23 March 2023 * * Date : 30 May 2023 *
* Website : http://www.angusj.com * * Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 * * Copyright : Angus Johnson 2010-2023 *
* Purpose : This module exports the Clipper2 Library (ie DLL/so) * * Purpose : This module exports the Clipper2 Library (ie DLL/so) *
@ -157,14 +157,14 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
int precision = 2, double miter_limit = 2.0, int precision = 2, double miter_limit = 2.0,
double arc_tolerance = 0.0, bool reverse_solution = false); double arc_tolerance = 0.0, bool reverse_solution = false);
// ExecuteRectClip & ExecuteRectClipLines: // RectClip & RectClipLines:
EXTERN_DLL_EXPORT CPaths64 ExecuteRectClip64(const CRect64& rect, EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
const CPaths64 paths, bool convex_only = false); const CPaths64 paths, bool convex_only = false);
EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect, EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
const CPathsD paths, int precision = 2, bool convex_only = false); const CPathsD paths, int precision = 2, bool convex_only = false);
EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(const CRect64& rect, EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
const CPaths64 paths); const CPaths64 paths);
EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(const CRectD& rect, EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
const CPathsD paths, int precision = 2); const CPathsD paths, int precision = 2);
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
@ -381,19 +381,17 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
return CreateCPathsD(result, 1/scale); return CreateCPathsD(result, 1/scale);
} }
EXTERN_DLL_EXPORT CPaths64 ExecuteRectClip64(const CRect64& rect, EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths)
const CPaths64 paths, bool convex_only)
{ {
if (CRectIsEmpty(rect) || !paths) return nullptr; if (CRectIsEmpty(rect) || !paths) return nullptr;
Rect64 r64 = CRectToRect(rect); Rect64 r64 = CRectToRect(rect);
class RectClip rc(r64); class RectClip64 rc(r64);
Paths64 pp = ConvertCPaths64(paths); Paths64 pp = ConvertCPaths64(paths);
Paths64 result = rc.Execute(pp, convex_only); Paths64 result = rc.Execute(pp);
return CreateCPaths64(result); return CreateCPaths64(result);
} }
EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect, EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision)
const CPathsD paths, int precision, bool convex_only)
{ {
if (CRectIsEmpty(rect) || !paths) return nullptr; if (CRectIsEmpty(rect) || !paths) return nullptr;
if (precision < -8 || precision > 8) return nullptr; if (precision < -8 || precision > 8) return nullptr;
@ -402,30 +400,30 @@ EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect,
RectD r = CRectToRect(rect); RectD r = CRectToRect(rect);
Rect64 rec = ScaleRect<int64_t, double>(r, scale); Rect64 rec = ScaleRect<int64_t, double>(r, scale);
Paths64 pp = ConvertCPathsD(paths, scale); Paths64 pp = ConvertCPathsD(paths, scale);
class RectClip rc(rec); class RectClip64 rc(rec);
Paths64 result = rc.Execute(pp, convex_only); Paths64 result = rc.Execute(pp);
return CreateCPathsD(result, 1/scale); return CreateCPathsD(result, 1/scale);
} }
EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(const CRect64& rect, EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
const CPaths64 paths) const CPaths64 paths)
{ {
if (CRectIsEmpty(rect) || !paths) return nullptr; if (CRectIsEmpty(rect) || !paths) return nullptr;
Rect64 r = CRectToRect(rect); Rect64 r = CRectToRect(rect);
class RectClipLines rcl (r); class RectClipLines64 rcl (r);
Paths64 pp = ConvertCPaths64(paths); Paths64 pp = ConvertCPaths64(paths);
Paths64 result = rcl.Execute(pp); Paths64 result = rcl.Execute(pp);
return CreateCPaths64(result); return CreateCPaths64(result);
} }
EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(const CRectD& rect, EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
const CPathsD paths, int precision) const CPathsD paths, int precision)
{ {
if (CRectIsEmpty(rect) || !paths) return nullptr; if (CRectIsEmpty(rect) || !paths) return nullptr;
if (precision < -8 || precision > 8) return nullptr; if (precision < -8 || precision > 8) return nullptr;
const double scale = std::pow(10, precision); const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale); Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale);
class RectClipLines rcl(r); class RectClipLines64 rcl(r);
Paths64 pp = ConvertCPathsD(paths, scale); Paths64 pp = ConvertCPathsD(paths, scale);
Paths64 result = rcl.Execute(pp); Paths64 result = rcl.Execute(pp);
return CreateCPathsD(result, 1/scale); return CreateCPathsD(result, 1/scale);

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 21 April 2023 * * Date : 26 May 2023 *
* Website : http://www.angusj.com * * Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 * * Copyright : Angus Johnson 2010-2023 *
* Purpose : This module provides a simple interface to the Clipper Library * * Purpose : This module provides a simple interface to the Clipper Library *
@ -161,60 +161,61 @@ namespace Clipper2Lib {
return ScalePaths<double, int64_t>(solution, 1 / scale, error_code); return ScalePaths<double, int64_t>(solution, 1 / scale, error_code);
} }
inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy) template <typename T>
inline Path<T> TranslatePath(const Path<T>& path, T dx, T dy)
{ {
Path64 result; Path<T> result;
result.reserve(path.size()); result.reserve(path.size());
std::transform(path.begin(), path.end(), back_inserter(result), std::transform(path.begin(), path.end(), back_inserter(result),
[dx, dy](const auto& pt) { return Point64(pt.x + dx, pt.y +dy); }); [dx, dy](const auto& pt) { return Point<T>(pt.x + dx, pt.y +dy); });
return result; return result;
} }
inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
{
return TranslatePath<int64_t>(path, dx, dy);
}
inline PathD TranslatePath(const PathD& path, double dx, double dy) inline PathD TranslatePath(const PathD& path, double dx, double dy)
{ {
PathD result; return TranslatePath<double>(path, dx, dy);
result.reserve(path.size()); }
std::transform(path.begin(), path.end(), back_inserter(result),
[dx, dy](const auto& pt) { return PointD(pt.x + dx, pt.y + dy); }); template <typename T>
inline Paths<T> TranslatePaths(const Paths<T>& paths, T dx, T dy)
{
Paths<T> result;
result.reserve(paths.size());
std::transform(paths.begin(), paths.end(), back_inserter(result),
[dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
return result; return result;
} }
inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy) inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
{ {
Paths64 result; return TranslatePaths<int64_t>(paths, dx, dy);
result.reserve(paths.size());
std::transform(paths.begin(), paths.end(), back_inserter(result),
[dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
return result;
} }
inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy) inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
{ {
PathsD result; return TranslatePaths<double>(paths, dx, dy);
result.reserve(paths.size());
std::transform(paths.begin(), paths.end(), back_inserter(result),
[dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
return result;
} }
inline Paths64 ExecuteRectClip(const Rect64& rect, inline Paths64 RectClip(const Rect64& rect, const Paths64& paths)
const Paths64& paths, bool convex_only)
{ {
if (rect.IsEmpty() || paths.empty()) return Paths64(); if (rect.IsEmpty() || paths.empty()) return Paths64();
RectClip rc(rect); RectClip64 rc(rect);
return rc.Execute(paths, convex_only); return rc.Execute(paths);
} }
inline Paths64 ExecuteRectClip(const Rect64& rect, inline Paths64 RectClip(const Rect64& rect, const Path64& path)
const Path64& path, bool convex_only)
{ {
if (rect.IsEmpty() || path.empty()) return Paths64(); if (rect.IsEmpty() || path.empty()) return Paths64();
RectClip rc(rect); RectClip64 rc(rect);
return rc.Execute(Paths64{ path }, convex_only); return rc.Execute(Paths64{ path });
} }
inline PathsD ExecuteRectClip(const RectD& rect, inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2)
const PathsD& paths, bool convex_only, int precision = 2)
{ {
if (rect.IsEmpty() || paths.empty()) return PathsD(); if (rect.IsEmpty() || paths.empty()) return PathsD();
int error_code = 0; int error_code = 0;
@ -222,32 +223,31 @@ namespace Clipper2Lib {
if (error_code) return PathsD(); if (error_code) return PathsD();
const double scale = std::pow(10, precision); const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale); Rect64 r = ScaleRect<int64_t, double>(rect, scale);
RectClip rc(r); RectClip64 rc(r);
Paths64 pp = ScalePaths<int64_t, double>(paths, scale, error_code); Paths64 pp = ScalePaths<int64_t, double>(paths, scale, error_code);
if (error_code) return PathsD(); // ie: error_code result is lost if (error_code) return PathsD(); // ie: error_code result is lost
return ScalePaths<double, int64_t>( return ScalePaths<double, int64_t>(
rc.Execute(pp, convex_only), 1 / scale, error_code); rc.Execute(pp), 1 / scale, error_code);
} }
inline PathsD ExecuteRectClip(const RectD& rect, inline PathsD RectClip(const RectD& rect, const PathD& path, int precision = 2)
const PathD& path, bool convex_only, int precision = 2)
{ {
return ExecuteRectClip(rect, PathsD{ path }, convex_only, precision); return RectClip(rect, PathsD{ path }, precision);
} }
inline Paths64 ExecuteRectClipLines(const Rect64& rect, const Paths64& lines) inline Paths64 RectClipLines(const Rect64& rect, const Paths64& lines)
{ {
if (rect.IsEmpty() || lines.empty()) return Paths64(); if (rect.IsEmpty() || lines.empty()) return Paths64();
RectClipLines rcl(rect); RectClipLines64 rcl(rect);
return rcl.Execute(lines); return rcl.Execute(lines);
} }
inline Paths64 ExecuteRectClipLines(const Rect64& rect, const Path64& line) inline Paths64 RectClipLines(const Rect64& rect, const Path64& line)
{ {
return ExecuteRectClipLines(rect, Paths64{ line }); return RectClipLines(rect, Paths64{ line });
} }
inline PathsD ExecuteRectClipLines(const RectD& rect, const PathsD& lines, int precision = 2) inline PathsD RectClipLines(const RectD& rect, const PathsD& lines, int precision = 2)
{ {
if (rect.IsEmpty() || lines.empty()) return PathsD(); if (rect.IsEmpty() || lines.empty()) return PathsD();
int error_code = 0; int error_code = 0;
@ -255,16 +255,16 @@ namespace Clipper2Lib {
if (error_code) return PathsD(); if (error_code) return PathsD();
const double scale = std::pow(10, precision); const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale); Rect64 r = ScaleRect<int64_t, double>(rect, scale);
RectClipLines rcl(r); RectClipLines64 rcl(r);
Paths64 p = ScalePaths<int64_t, double>(lines, scale, error_code); Paths64 p = ScalePaths<int64_t, double>(lines, scale, error_code);
if (error_code) return PathsD(); if (error_code) return PathsD();
p = rcl.Execute(p); p = rcl.Execute(p);
return ScalePaths<double, int64_t>(p, 1 / scale, error_code); return ScalePaths<double, int64_t>(p, 1 / scale, error_code);
} }
inline PathsD ExecuteRectClipLines(const RectD& rect, const PathD& line, int precision = 2) inline PathsD RectClipLines(const RectD& rect, const PathD& line, int precision = 2)
{ {
return ExecuteRectClipLines(rect, PathsD{ line }, precision); return RectClipLines(rect, PathsD{ line }, precision);
} }
namespace details namespace details
@ -290,14 +290,9 @@ namespace Clipper2Lib {
{ {
// return false if this child isn't fully contained by its parent // return false if this child isn't fully contained by its parent
// the following algorithm is a bit too crude, and doesn't account // checking for a single vertex outside is a bit too crude since
// for rounding errors. A better algorithm is to return false when // it doesn't account for rounding errors. It's better to check
// consecutive vertices are found outside the parent's polygon. // for consecutive vertices found outside the parent's polygon.
//const Path64& path = pp.Polygon();
//if (std::any_of(child->Polygon().cbegin(), child->Polygon().cend(),
// [path](const auto& pt) {return (PointInPolygon(pt, path) ==
// PointInPolygonResult::IsOutside); })) return false;
int outsideCnt = 0; int outsideCnt = 0;
for (const Point64& pt : child->Polygon()) for (const Point64& pt : child->Polygon())
@ -490,6 +485,39 @@ namespace Clipper2Lib {
return result; return result;
} }
#ifdef USINGZ
template<typename T2, std::size_t N>
inline Path64 MakePathZ(const T2(&list)[N])
{
static_assert(N % 3 == 0 && std::numeric_limits<T2>::is_integer,
"MakePathZ requires integer values in multiples of 3");
std::size_t size = N / 3;
Path64 result(size);
for (size_t i = 0; i < size; ++i)
result[i] = Point64(list[i * 3],
list[i * 3 + 1], list[i * 3 + 2]);
return result;
}
template<typename T2, std::size_t N>
inline PathD MakePathZD(const T2(&list)[N])
{
static_assert(N % 3 == 0,
"MakePathZD requires values in multiples of 3");
std::size_t size = N / 3;
PathD result(size);
if constexpr (std::numeric_limits<T2>::is_integer)
for (size_t i = 0; i < size; ++i)
result[i] = PointD(list[i * 3],
list[i * 3 + 1], list[i * 3 + 2]);
else
for (size_t i = 0; i < size; ++i)
result[i] = PointD(list[i * 3], list[i * 3 + 1],
static_cast<int64_t>(list[i * 3 + 2]));
return result;
}
#endif
inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false) inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false)
{ {
size_t len = p.size(); size_t len = p.size();

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 22 March 2023 * * Date : 15 May 2023 *
* Website : http://www.angusj.com * * Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 * * Copyright : Angus Johnson 2010-2023 *
* Purpose : Path Offset (Inflate/Shrink) * * Purpose : Path Offset (Inflate/Shrink) *
@ -24,6 +24,7 @@ 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:
@ -43,7 +44,6 @@ private:
int error_code_ = 0; int error_code_ = 0;
double delta_ = 0.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_sin_ = 0.0;
@ -62,6 +62,7 @@ private:
#ifdef USINGZ #ifdef USINGZ
ZCallback64 zCallback64_ = nullptr; ZCallback64 zCallback64_ = nullptr;
#endif #endif
DeltaCallback64 deltaCallback64_ = nullptr;
void DoSquare(Group& group, const Path64& path, size_t j, size_t k); void DoSquare(Group& group, const Path64& path, size_t j, size_t k);
void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a); void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a);
@ -70,7 +71,7 @@ private:
void OffsetPolygon(Group& group, Path64& path); void OffsetPolygon(Group& group, Path64& path);
void OffsetOpenJoined(Group& group, Path64& path); void OffsetOpenJoined(Group& group, Path64& path);
void OffsetOpenPath(Group& group, Path64& path); void OffsetOpenPath(Group& group, Path64& path);
void OffsetPoint(Group& group, Path64& path, size_t j, size_t& k); void OffsetPoint(Group& group, Path64& path, size_t j, size_t k);
void DoGroupOffset(Group &group); void DoGroupOffset(Group &group);
void ExecuteInternal(double delta); void ExecuteInternal(double delta);
public: public:
@ -91,6 +92,7 @@ public:
void Execute(double delta, Paths64& paths); void Execute(double delta, Paths64& paths);
void Execute(double delta, PolyTree64& polytree); 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; }
@ -108,6 +110,8 @@ public:
#ifdef USINGZ #ifdef USINGZ
void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; } void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; }
#endif #endif
void SetDeltaCallback(DeltaCallback64 cb) { deltaCallback64_ = cb; }
}; };
} }

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 9 February 2023 * * Date : 30 May 2023 *
* Website : http://www.angusj.com * * Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 * * Copyright : Angus Johnson 2010-2023 *
* Purpose : FAST rectangular clipping * * Purpose : FAST rectangular clipping *
@ -34,10 +34,10 @@ namespace Clipper2Lib
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// RectClip // RectClip64
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class RectClip { class RectClip64 {
private: private:
void ExecuteInternal(const Path64& path); void ExecuteInternal(const Path64& path);
Path64 GetPath(OutPt2*& op); Path64 GetPath(OutPt2*& op);
@ -58,23 +58,23 @@ namespace Clipper2Lib
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:
explicit RectClip(const Rect64& rect) : explicit RectClip64(const Rect64& rect) :
rect_(rect), rect_(rect),
rect_as_path_(rect.AsPath()), rect_as_path_(rect.AsPath()),
rect_mp_(rect.MidPoint()) {} rect_mp_(rect.MidPoint()) {}
Paths64 Execute(const Paths64& paths, bool convex_only = false); Paths64 Execute(const Paths64& paths);
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// RectClipLines // RectClipLines64
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class RectClipLines : public RectClip { class RectClipLines64 : public RectClip64 {
private: private:
void ExecuteInternal(const Path64& path); void ExecuteInternal(const Path64& path);
Path64 GetPath(OutPt2*& op); Path64 GetPath(OutPt2*& op);
public: public:
explicit RectClipLines(const Rect64& rect) : RectClip(rect) {}; explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {};
Paths64 Execute(const Paths64& paths); Paths64 Execute(const Paths64& paths);
}; };

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 23 April 2023 * * Date : 20 May 2023 *
* Website : http://www.angusj.com * * Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 * * Copyright : Angus Johnson 2010-2023 *
* Purpose : This is the main polygon clipping module * * Purpose : This is the main polygon clipping module *
@ -419,7 +419,6 @@ namespace Clipper2Lib {
return outrec; return outrec;
} }
inline void UncoupleOutRec(Active ae) inline void UncoupleOutRec(Active ae)
{ {
OutRec* outrec = ae.outrec; OutRec* outrec = ae.outrec;
@ -484,6 +483,156 @@ namespace Clipper2Lib {
outrec->owner = new_owner; outrec->owner = new_owner;
} }
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void AddLocMin(LocalMinimaList& list,
Vertex& vert, PathType polytype, bool is_open)
{
//make sure the vertex is added only once ...
if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return;
vert.flags = (vert.flags | VertexFlags::LocalMin);
list.push_back(std::make_unique <LocalMinima>(&vert, polytype, is_open));
}
void AddPaths_(const Paths64& paths, PathType polytype, bool is_open,
std::vector<Vertex*>& vertexLists, LocalMinimaList& locMinList)
{
const auto total_vertex_count =
std::accumulate(paths.begin(), paths.end(), 0,
[](const auto& a, const Path64& path)
{return a + static_cast<unsigned>(path.size()); });
if (total_vertex_count == 0) return;
Vertex* vertices = new Vertex[total_vertex_count], * v = vertices;
for (const Path64& path : paths)
{
//for each path create a circular double linked list of vertices
Vertex* v0 = v, * curr_v = v, * prev_v = nullptr;
if (path.empty())
continue;
v->prev = nullptr;
int cnt = 0;
for (const Point64& pt : path)
{
if (prev_v)
{
if (prev_v->pt == pt) continue; // ie skips duplicates
prev_v->next = curr_v;
}
curr_v->prev = prev_v;
curr_v->pt = pt;
curr_v->flags = VertexFlags::None;
prev_v = curr_v++;
cnt++;
}
if (!prev_v || !prev_v->prev) continue;
if (!is_open && prev_v->pt == v0->pt)
prev_v = prev_v->prev;
prev_v->next = v0;
v0->prev = prev_v;
v = curr_v; // ie get ready for next path
if (cnt < 2 || (cnt == 2 && !is_open)) continue;
//now find and assign local minima
bool going_up, going_up0;
if (is_open)
{
curr_v = v0->next;
while (curr_v != v0 && curr_v->pt.y == v0->pt.y)
curr_v = curr_v->next;
going_up = curr_v->pt.y <= v0->pt.y;
if (going_up)
{
v0->flags = VertexFlags::OpenStart;
AddLocMin(locMinList , *v0, polytype, true);
}
else
v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax;
}
else // closed path
{
prev_v = v0->prev;
while (prev_v != v0 && prev_v->pt.y == v0->pt.y)
prev_v = prev_v->prev;
if (prev_v == v0)
continue; // only open paths can be completely flat
going_up = prev_v->pt.y > v0->pt.y;
}
going_up0 = going_up;
prev_v = v0;
curr_v = v0->next;
while (curr_v != v0)
{
if (curr_v->pt.y > prev_v->pt.y && going_up)
{
prev_v->flags = (prev_v->flags | VertexFlags::LocalMax);
going_up = false;
}
else if (curr_v->pt.y < prev_v->pt.y && !going_up)
{
going_up = true;
AddLocMin(locMinList, *prev_v, polytype, is_open);
}
prev_v = curr_v;
curr_v = curr_v->next;
}
if (is_open)
{
prev_v->flags = prev_v->flags | VertexFlags::OpenEnd;
if (going_up)
prev_v->flags = prev_v->flags | VertexFlags::LocalMax;
else
AddLocMin(locMinList, *prev_v, polytype, is_open);
}
else if (going_up != going_up0)
{
if (going_up0) AddLocMin(locMinList, *prev_v, polytype, false);
else prev_v->flags = prev_v->flags | VertexFlags::LocalMax;
}
} // end processing current path
vertexLists.emplace_back(vertices);
}
//------------------------------------------------------------------------------
// ReuseableDataContainer64 methods ...
//------------------------------------------------------------------------------
void ReuseableDataContainer64::AddLocMin(Vertex& vert, PathType polytype, bool is_open)
{
//make sure the vertex is added only once ...
if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return;
vert.flags = (vert.flags | VertexFlags::LocalMin);
minima_list_.push_back(std::make_unique <LocalMinima>(&vert, polytype, is_open));
}
void ReuseableDataContainer64::AddPaths(const Paths64& paths,
PathType polytype, bool is_open)
{
AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_);
}
ReuseableDataContainer64::~ReuseableDataContainer64()
{
Clear();
}
void ReuseableDataContainer64::Clear()
{
minima_list_.clear();
for (auto v : vertex_lists_) delete[] v;
vertex_lists_.clear();
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// ClipperBase methods ... // ClipperBase methods ...
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -576,113 +725,26 @@ namespace Clipper2Lib {
AddPaths(tmp, polytype, is_open); AddPaths(tmp, polytype, is_open);
} }
void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open) void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open)
{ {
if (is_open) has_open_paths_ = true; if (is_open) has_open_paths_ = true;
minima_list_sorted_ = false; minima_list_sorted_ = false;
AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_);
}
const auto total_vertex_count = void ClipperBase::AddReuseableData(const ReuseableDataContainer64& reuseable_data)
std::accumulate(paths.begin(), paths.end(), 0, {
[](const auto& a, const Path64& path) // nb: reuseable_data will continue to own the vertices
{return a + static_cast<unsigned>(path.size());}); // and remains responsible for their clean up.
if (total_vertex_count == 0) return; succeeded_ = false;
minima_list_sorted_ = false;
Vertex* vertices = new Vertex[total_vertex_count], * v = vertices; LocalMinimaList::const_iterator i;
for (const Path64& path : paths) for (i = reuseable_data.minima_list_.cbegin(); i != reuseable_data.minima_list_.cend(); ++i)
{ {
//for each path create a circular double linked list of vertices minima_list_.push_back(std::make_unique <LocalMinima>((*i)->vertex, (*i)->polytype, (*i)->is_open));
Vertex* v0 = v, * curr_v = v, * prev_v = nullptr; if ((*i)->is_open) has_open_paths_ = true;
}
if (path.empty()) }
continue;
v->prev = nullptr;
int cnt = 0;
for (const Point64& pt : path)
{
if (prev_v)
{
if (prev_v->pt == pt) continue; // ie skips duplicates
prev_v->next = curr_v;
}
curr_v->prev = prev_v;
curr_v->pt = pt;
curr_v->flags = VertexFlags::None;
prev_v = curr_v++;
cnt++;
}
if (!prev_v || !prev_v->prev) continue;
if (!is_open && prev_v->pt == v0->pt)
prev_v = prev_v->prev;
prev_v->next = v0;
v0->prev = prev_v;
v = curr_v; // ie get ready for next path
if (cnt < 2 || (cnt == 2 && !is_open)) continue;
//now find and assign local minima
bool going_up, going_up0;
if (is_open)
{
curr_v = v0->next;
while (curr_v != v0 && curr_v->pt.y == v0->pt.y)
curr_v = curr_v->next;
going_up = curr_v->pt.y <= v0->pt.y;
if (going_up)
{
v0->flags = VertexFlags::OpenStart;
AddLocMin(*v0, polytype, true);
}
else
v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax;
}
else // closed path
{
prev_v = v0->prev;
while (prev_v != v0 && prev_v->pt.y == v0->pt.y)
prev_v = prev_v->prev;
if (prev_v == v0)
continue; // only open paths can be completely flat
going_up = prev_v->pt.y > v0->pt.y;
}
going_up0 = going_up;
prev_v = v0;
curr_v = v0->next;
while (curr_v != v0)
{
if (curr_v->pt.y > prev_v->pt.y && going_up)
{
prev_v->flags = (prev_v->flags | VertexFlags::LocalMax);
going_up = false;
}
else if (curr_v->pt.y < prev_v->pt.y && !going_up)
{
going_up = true;
AddLocMin(*prev_v, polytype, is_open);
}
prev_v = curr_v;
curr_v = curr_v->next;
}
if (is_open)
{
prev_v->flags = prev_v->flags | VertexFlags::OpenEnd;
if (going_up)
prev_v->flags = prev_v->flags | VertexFlags::LocalMax;
else
AddLocMin(*prev_v, polytype, is_open);
}
else if (going_up != going_up0)
{
if (going_up0) AddLocMin(*prev_v, polytype, false);
else prev_v->flags = prev_v->flags | VertexFlags::LocalMax;
}
} // end processing current path
vertex_lists_.emplace_back(vertices);
} // end AddPaths
void ClipperBase::InsertScanline(int64_t y) void ClipperBase::InsertScanline(int64_t y)
{ {
@ -1315,6 +1377,7 @@ namespace Clipper2Lib {
result->owner = nullptr; result->owner = nullptr;
result->polypath = nullptr; result->polypath = nullptr;
result->is_open = false; result->is_open = false;
result->splits = nullptr;
return result; return result;
} }
@ -1596,17 +1659,14 @@ namespace Clipper2Lib {
default: if (std::abs(edge_c->wind_cnt) != 1) return nullptr; break; default: if (std::abs(edge_c->wind_cnt) != 1) return nullptr; break;
} }
OutPt* resultOp;
//toggle contribution ... //toggle contribution ...
if (IsHotEdge(*edge_o)) if (IsHotEdge(*edge_o))
{ {
OutPt* resultOp = AddOutPt(*edge_o, pt); resultOp = AddOutPt(*edge_o, pt);
#ifdef USINGZ
if (zCallback_) SetZ(e1, e2, resultOp->pt);
#endif
if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr; if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr;
else edge_o->outrec->back_edge = nullptr; else edge_o->outrec->back_edge = nullptr;
edge_o->outrec = nullptr; edge_o->outrec = nullptr;
return resultOp;
} }
//horizontal edges can pass under open paths at a LocMins //horizontal edges can pass under open paths at a LocMins
@ -1626,11 +1686,16 @@ namespace Clipper2Lib {
return e3->outrec->pts; return e3->outrec->pts;
} }
else else
return StartOpenPath(*edge_o, pt); resultOp = StartOpenPath(*edge_o, pt);
} }
else else
return StartOpenPath(*edge_o, pt); resultOp = StartOpenPath(*edge_o, pt);
}
#ifdef USINGZ
if (zCallback_) SetZ(*edge_o, *edge_c, resultOp->pt);
#endif
return resultOp;
} // end of an open path intersection
//MANAGING CLOSED PATHS FROM HERE ON //MANAGING CLOSED PATHS FROM HERE ON
@ -2111,7 +2176,7 @@ namespace Clipper2Lib {
if (or1 == or2) if (or1 == or2)
{ {
or2 = new OutRec(); or2 = NewOutRec();
or2->pts = op1b; or2->pts = op1b;
FixOutRecPts(or2); FixOutRecPts(or2);
if (or1->pts->outrec == or2) if (or1->pts->outrec == or2)
@ -2123,7 +2188,11 @@ namespace Clipper2Lib {
if (using_polytree_) if (using_polytree_)
{ {
if (Path1InsidePath2(or2->pts, or1->pts)) if (Path1InsidePath2(or2->pts, or1->pts))
{
SetOwner(or2, or1); SetOwner(or2, or1);
if (!or1->splits) or1->splits = new OutRecList();
or1->splits->push_back(or2); //(#520)
}
else if (Path1InsidePath2(or1->pts, or2->pts)) else if (Path1InsidePath2(or1->pts, or2->pts))
SetOwner(or1, or2); SetOwner(or1, or2);
else else
@ -2135,8 +2204,6 @@ namespace Clipper2Lib {
} }
else else
or2->owner = or1; or2->owner = or1;
outrec_list_.push_back(or2);
} }
else else
{ {
@ -2763,14 +2830,13 @@ namespace Clipper2Lib {
return true; return true;
} }
bool ClipperBase::CheckSplitOwner(OutRec* outrec) bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits)
{ {
for (auto s : *outrec->owner->splits) for (auto split : *splits)
{ {
OutRec* split = GetRealOutRec(s); if(split == outrec || split == outrec->owner) continue;
if (split && split != outrec && else if (split->splits && CheckSplitOwner(outrec, split->splits)) return true;
split != outrec->owner && CheckBounds(split) && else if (CheckBounds(split) && split->bounds.Contains(outrec->bounds) &&
split->bounds.Contains(outrec->bounds) &&
Path1InsidePath2(outrec->pts, split->pts)) Path1InsidePath2(outrec->pts, split->pts))
{ {
outrec->owner = split; //found in split outrec->owner = split; //found in split
@ -2789,7 +2855,7 @@ namespace Clipper2Lib {
while (outrec->owner) while (outrec->owner)
{ {
if (outrec->owner->splits && CheckSplitOwner(outrec)) break; if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break;
if (outrec->owner->pts && CheckBounds(outrec->owner) && if (outrec->owner->pts && CheckBounds(outrec->owner) &&
outrec->owner->bounds.Contains(outrec->bounds) && outrec->owner->bounds.Contains(outrec->bounds) &&
Path1InsidePath2(outrec->pts, outrec->owner->pts)) break; Path1InsidePath2(outrec->pts, outrec->owner->pts)) break;

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 8 April 2023 * * Date : 26 May 2023 *
* Website : http://www.angusj.com * * Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 * * Copyright : Angus Johnson 2010-2023 *
* Purpose : Path Offset (Inflate/Shrink) * * Purpose : Path Offset (Inflate/Shrink) *
@ -205,15 +205,17 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t
{ {
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);
@ -260,6 +262,20 @@ void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k
void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle) void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle)
{ {
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_) :
std::log10(2 + abs_delta) * default_arc_tolerance);
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];
PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_); PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
@ -287,7 +303,7 @@ void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k
group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); group.path.push_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, 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)
@ -302,12 +318,23 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& 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;
if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, j, k);
if (std::fabs(group_delta_) <= floating_point_tolerance)
{
group.path.push_back(path[j]);
return;
}
if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424, #526)
{
DoMiter(group, path, j, k, cos_a);
}
else if (cos_a > -0.99 && (sin_a * group_delta_ < 0))
{ {
// is concave // is concave
group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_));
// this extra point is the only (simple) way to ensure that // this extra point is the only (simple) way to ensure that
// path reversals are fully cleaned with the trailing clipper // path reversals are fully cleaned with the trailing clipper
group.path.push_back(path[j]); // (#405) group.path.push_back(path[j]); // (#405)
group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_));
} }
@ -317,22 +344,16 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k)
if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a); if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
else DoSquare(group, path, j, k); else DoSquare(group, path, j, k);
} }
else if (cos_a > 0.9998) else if (cos_a > 0.99 || join_type_ == JoinType::Square) // 0.99 ~= 8.1 deg.
// almost straight - less than 1 degree (#424)
DoMiter(group, path, j, k, cos_a);
else if (cos_a > 0.99 || join_type_ == JoinType::Square)
//angle less than 8 degrees or squared joins
DoSquare(group, path, j, k); DoSquare(group, path, j, k);
else else
DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
k = j;
} }
void ClipperOffset::OffsetPolygon(Group& group, Path64& path) void ClipperOffset::OffsetPolygon(Group& group, Path64& path)
{ {
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); group.paths_out.push_back(group.path);
} }
@ -354,34 +375,40 @@ void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path)
void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) void ClipperOffset::OffsetOpenPath(Group& group, Path64& path)
{ {
// 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)
group.path.push_back(path[0]);
else
{ {
case EndType::Butt: switch (end_type_)
{
case EndType::Butt:
#ifdef USINGZ #ifdef USINGZ
group.path.push_back(Point64( group.path.push_back(Point64(
path[0].x - norms[0].x * group_delta_, path[0].x - norms[0].x * group_delta_,
path[0].y - norms[0].y * group_delta_, path[0].y - norms[0].y * group_delta_,
path[0].z)); path[0].z));
#else #else
group.path.push_back(Point64( group.path.push_back(Point64(
path[0].x - norms[0].x * group_delta_, path[0].x - norms[0].x * group_delta_,
path[0].y - norms[0].y * group_delta_)); path[0].y - norms[0].y * group_delta_));
#endif #endif
group.path.push_back(GetPerpendic(path[0], norms[0], 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(group, path, 0, 0, PI);
break; break;
default: default:
DoSquare(group, 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)
@ -389,31 +416,39 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path)
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)
group.path.push_back(path[highI]);
else
{ {
case EndType::Butt: switch (end_type_)
{
case EndType::Butt:
#ifdef USINGZ #ifdef USINGZ
group.path.push_back(Point64( group.path.push_back(Point64(
path[highI].x - norms[highI].x * group_delta_, path[highI].x - norms[highI].x * group_delta_,
path[highI].y - norms[highI].y * group_delta_, path[highI].y - norms[highI].y * group_delta_,
path[highI].z)); path[highI].z));
#else #else
group.path.push_back(Point64( group.path.push_back(Point64(
path[highI].x - norms[highI].x * group_delta_, path[highI].x - norms[highI].x * group_delta_,
path[highI].y - norms[highI].y * group_delta_)); path[highI].y - norms[highI].y * group_delta_));
#endif #endif
group.path.push_back(GetPerpendic(path[highI], norms[highI], 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(group, path, highI, highI, PI);
break; break;
default: default:
DoSquare(group, 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, k = 0; j > 0; k = j, --j)
OffsetPoint(group, path, i, k); OffsetPoint(group, path, j, k);
group.paths_out.push_back(group.path); group.paths_out.push_back(group.path);
} }
@ -439,10 +474,10 @@ void ClipperOffset::DoGroupOffset(Group& group)
group.is_reversed = false; group.is_reversed = false;
group_delta_ = std::abs(delta_) * 0.5; group_delta_ = std::abs(delta_) * 0.5;
} }
abs_group_delta_ = std::fabs(group_delta_);
double abs_delta = std::fabs(group_delta_);
// do range checking // do range checking
if (!IsSafeOffset(r, abs_group_delta_)) if (!IsSafeOffset(r, abs_delta))
{ {
DoError(range_error_i); DoError(range_error_i);
error_code_ |= range_error_i; error_code_ |= range_error_i;
@ -452,24 +487,23 @@ void ClipperOffset::DoGroupOffset(Group& group)
join_type_ = group.join_type; join_type_ = group.join_type;
end_type_ = group.end_type; end_type_ = group.end_type;
//calculate a sensible number of steps (for 360 deg for the given offset if (!deltaCallback64_ &&
if (group.join_type == JoinType::Round || group.end_type == EndType::Round) (group.join_type == JoinType::Round || group.end_type == EndType::Round))
{ {
//calculate a sensible number of steps (for 360 deg for the given offset)
// arcTol - when arc_tolerance_ is undefined (0), the amount of // arcTol - when arc_tolerance_ is undefined (0), the amount of
// curve imprecision that's allowed is based on the size of the // curve imprecision that's allowed is based on the size of the
// offset (delta). Obviously very large offsets will almost always // offset (delta). Obviously very large offsets will almost always
// require much less precision. See also offset_triginometry2.svg // require much less precision. See also offset_triginometry2.svg
double arcTol = (arc_tolerance_ > floating_point_tolerance ? double arcTol = (arc_tolerance_ > floating_point_tolerance ?
std::min(abs_group_delta_, arc_tolerance_) : std::min(abs_delta, arc_tolerance_) :
std::log10(2 + abs_group_delta_) * default_arc_tolerance); std::log10(2 + abs_delta) * default_arc_tolerance);
double steps_per_360 = PI / std::acos(1 - arcTol / abs_group_delta_);
if (steps_per_360 > abs_group_delta_ * PI)
steps_per_360 = abs_group_delta_ * PI; //ie avoids excessive precision
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_sin_ = std::sin(2 * PI / steps_per_360);
step_cos_ = std::cos(2 * PI / steps_per_360); step_cos_ = std::cos(2 * PI / steps_per_360);
if (group_delta_ < 0.0) step_sin_ = -step_sin_; if (group_delta_ < 0.0) step_sin_ = -step_sin_;
steps_per_rad_ = steps_per_360 / (2 *PI); steps_per_rad_ = steps_per_360 / (2 * PI);
} }
bool is_joined = bool is_joined =
@ -478,7 +512,7 @@ void ClipperOffset::DoGroupOffset(Group& group)
Paths64::iterator path_iter; Paths64::iterator path_iter;
for(path_iter = group.paths_in.begin(); path_iter != group.paths_in.end(); ++path_iter) for(path_iter = group.paths_in.begin(); path_iter != group.paths_in.end(); ++path_iter)
{ {
auto path = *path_iter; Path64 &path = *path_iter;
StripDuplicates(path, is_joined); StripDuplicates(path, is_joined);
Path64::size_type cnt = path.size(); Path64::size_type cnt = path.size();
if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon)) if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon))
@ -491,7 +525,7 @@ void ClipperOffset::DoGroupOffset(Group& group)
//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); group.path = Ellipse(path[0], radius, radius);
#ifdef USINGZ #ifdef USINGZ
for (auto& p : group.path) p.z = path[0].z; for (auto& p : group.path) p.z = path[0].z;
@ -499,7 +533,7 @@ void ClipperOffset::DoGroupOffset(Group& group)
} }
else else
{ {
int d = (int)std::ceil(abs_group_delta_); int d = (int)std::ceil(abs_delta);
r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d); r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d);
group.path = r.AsPath(); group.path = r.AsPath();
#ifdef USINGZ #ifdef USINGZ
@ -568,6 +602,7 @@ void ClipperOffset::Execute(double delta, Paths64& paths)
if (!solution.size()) return; if (!solution.size()) return;
paths = solution; paths = solution;
/**/
//clean up self-intersections ... //clean up self-intersections ...
Clipper64 c; Clipper64 c;
c.PreserveCollinear = false; c.PreserveCollinear = false;
@ -583,6 +618,7 @@ void ClipperOffset::Execute(double delta, Paths64& paths)
c.Execute(ClipType::Union, FillRule::Negative, paths); c.Execute(ClipType::Union, FillRule::Negative, paths);
else else
c.Execute(ClipType::Union, FillRule::Positive, paths); c.Execute(ClipType::Union, FillRule::Positive, paths);
/**/
} }
@ -610,4 +646,10 @@ void ClipperOffset::Execute(double delta, PolyTree64& polytree)
c.Execute(ClipType::Union, FillRule::Positive, polytree); c.Execute(ClipType::Union, FillRule::Positive, polytree);
} }
void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths)
{
deltaCallback64_ = delta_cb;
Execute(1.0, paths);
}
} // namespace } // namespace

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 22 April 2023 * * Date : 30 May 2023 *
* Website : http://www.angusj.com * * Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 * * Copyright : Angus Johnson 2010-2023 *
* Purpose : FAST rectangular clipping * * Purpose : FAST rectangular clipping *
@ -281,7 +281,7 @@ namespace Clipper2Lib {
// RectClip64 // RectClip64
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
OutPt2* RectClip::Add(Point64 pt, bool start_new) OutPt2* RectClip64::Add(Point64 pt, bool start_new)
{ {
// this method is only called by InternalExecute. // this method is only called by InternalExecute.
// Later splitting & rejoining won't create additional op's, // Later splitting & rejoining won't create additional op's,
@ -312,7 +312,7 @@ namespace Clipper2Lib {
return result; return result;
} }
void RectClip::AddCorner(Location prev, Location curr) void RectClip64::AddCorner(Location prev, Location curr)
{ {
if (HeadingClockwise(prev, curr)) if (HeadingClockwise(prev, curr))
Add(rect_as_path_[static_cast<int>(prev)]); Add(rect_as_path_[static_cast<int>(prev)]);
@ -320,7 +320,7 @@ namespace Clipper2Lib {
Add(rect_as_path_[static_cast<int>(curr)]); Add(rect_as_path_[static_cast<int>(curr)]);
} }
void RectClip::AddCorner(Location& loc, bool isClockwise) void RectClip64::AddCorner(Location& loc, bool isClockwise)
{ {
if (isClockwise) if (isClockwise)
{ {
@ -334,7 +334,7 @@ namespace Clipper2Lib {
} }
} }
void RectClip::GetNextLocation(const Path64& path, void RectClip64::GetNextLocation(const Path64& path,
Location& loc, int& i, int highI) Location& loc, int& i, int highI)
{ {
switch (loc) switch (loc)
@ -389,7 +389,7 @@ namespace Clipper2Lib {
} //switch } //switch
} }
void RectClip::ExecuteInternal(const Path64& path) void RectClip64::ExecuteInternal(const Path64& path)
{ {
int i = 0, highI = static_cast<int>(path.size()) - 1; int i = 0, highI = static_cast<int>(path.size()) - 1;
Location prev = Location::Inside, loc; Location prev = Location::Inside, loc;
@ -546,7 +546,7 @@ namespace Clipper2Lib {
} }
} }
void RectClip::CheckEdges() void RectClip64::CheckEdges()
{ {
for (size_t i = 0; i < results_.size(); ++i) for (size_t i = 0; i < results_.size(); ++i)
{ {
@ -606,7 +606,7 @@ namespace Clipper2Lib {
} }
} }
void RectClip::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw) void RectClip64::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw)
{ {
if (ccw.empty()) return; if (ccw.empty()) return;
bool isHorz = ((idx == 1) || (idx == 3)); bool isHorz = ((idx == 1) || (idx == 3));
@ -784,7 +784,7 @@ namespace Clipper2Lib {
} }
} }
Path64 RectClip::GetPath(OutPt2*& op) Path64 RectClip64::GetPath(OutPt2*& op)
{ {
if (!op || op->next == op->prev) return Path64(); if (!op || op->next == op->prev) return Path64();
@ -814,7 +814,7 @@ namespace Clipper2Lib {
return result; return result;
} }
Paths64 RectClip::Execute(const Paths64& paths, bool convex_only) Paths64 RectClip64::Execute(const Paths64& paths)
{ {
Paths64 result; Paths64 result;
if (rect_.IsEmpty()) return result; if (rect_.IsEmpty()) return result;
@ -833,12 +833,9 @@ namespace Clipper2Lib {
} }
ExecuteInternal(path); ExecuteInternal(path);
if (!convex_only) CheckEdges();
{ for (int i = 0; i < 4; ++i)
CheckEdges(); TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]);
for (int i = 0; i < 4; ++i)
TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]);
}
for (OutPt2*& op : results_) for (OutPt2*& op : results_)
{ {
@ -857,10 +854,10 @@ namespace Clipper2Lib {
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// RectClipLines // RectClipLines64
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
Paths64 RectClipLines::Execute(const Paths64& paths) Paths64 RectClipLines64::Execute(const Paths64& paths)
{ {
Paths64 result; Paths64 result;
if (rect_.IsEmpty()) return result; if (rect_.IsEmpty()) return result;
@ -886,7 +883,7 @@ namespace Clipper2Lib {
return result; return result;
} }
void RectClipLines::ExecuteInternal(const Path64& path) void RectClipLines64::ExecuteInternal(const Path64& path)
{ {
if (rect_.IsEmpty() || path.size() < 2) return; if (rect_.IsEmpty() || path.size() < 2) return;
@ -956,7 +953,7 @@ namespace Clipper2Lib {
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
} }
Path64 RectClipLines::GetPath(OutPt2*& op) Path64 RectClipLines64::GetPath(OutPt2*& op)
{ {
Path64 result; Path64 result;
if (!op || op == op->next) return result; if (!op || op == op->next) return result;