Clipper2 1.1.1 (#1039)

This is a minor update since ver Clipper2 1.1.0
Fixes a clipping bug introduced in the previous release (Issue #381).
This commit is contained in:
aismann 2023-01-29 02:45:10 +01:00 committed by GitHub
parent c87d5ccbb2
commit 09b32e26d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 406 additions and 290 deletions

View File

@ -37,7 +37,7 @@
## Clipper2 ## Clipper2
- [![Upstream](https://img.shields.io/github/v/tag/AngusJohnson/Clipper2?label=Upstream)](https://github.com/AngusJohnson/Clipper2) - [![Upstream](https://img.shields.io/github/v/tag/AngusJohnson/Clipper2?label=Upstream)](https://github.com/AngusJohnson/Clipper2)
- Version: 1.1.0 - Version: 1.1.1
- License: BSL-1.0 - License: BSL-1.0
## ConvertUTF ## ConvertUTF

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 14 January 2023 * * Date : 26 January 2023 *
* Website : http://www.angusj.com * * Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 * * Copyright : Angus Johnson 2010-2023 *
* Purpose : Core Clipper Library structures and functions * * Purpose : Core Clipper Library structures and functions *
@ -20,9 +20,23 @@
namespace Clipper2Lib namespace Clipper2Lib
{ {
#ifdef __cpp_exceptions #ifdef __cpp_exceptions
class Clipper2Exception : public std::exception {
public:
explicit Clipper2Exception(const char* description) :
m_descr(description) {}
virtual const char* what() const throw() { return m_descr.c_str(); }
private:
std::string m_descr;
};
static const char* precision_error = static const char* precision_error =
"Precision exceeds the permitted range"; "Precision exceeds the permitted range";
static const char* range_error =
"Values exceed permitted range";
#endif #endif
static const double PI = 3.141592653589793238; static const double PI = 3.141592653589793238;
@ -30,6 +44,9 @@ namespace Clipper2Lib
static const int64_t MIN_COORD = -MAX_COORD; static const int64_t MIN_COORD = -MAX_COORD;
static const int64_t INVALID = INT64_MAX; static const int64_t INVALID = INT64_MAX;
static const double MAX_DBL = (std::numeric_limits<double>::max)();
static const double MIN_DBL = (std::numeric_limits<double>::min)();
//By far the most widely used filling rules for polygons are EvenOdd //By far the most widely used filling rules for polygons are EvenOdd
//and NonZero, sometimes called Alternate and Winding respectively. //and NonZero, sometimes called Alternate and Winding respectively.
//https://en.wikipedia.org/wiki/Nonzero-rule //https://en.wikipedia.org/wiki/Nonzero-rule
@ -170,6 +187,177 @@ namespace Clipper2Lib
using Paths64 = std::vector< Path64>; using Paths64 = std::vector< Path64>;
using PathsD = std::vector< PathD>; using PathsD = std::vector< PathD>;
// Rect ------------------------------------------------------------------------
template <typename T>
struct Rect;
using Rect64 = Rect<int64_t>;
using RectD = Rect<double>;
template <typename T>
struct Rect {
T left;
T top;
T right;
T bottom;
Rect() :
left(0),
top(0),
right(0),
bottom(0) {}
Rect(T l, T t, T r, T b) :
left(l),
top(t),
right(r),
bottom(b) {}
T Width() const { return right - left; }
T Height() const { return bottom - top; }
void Width(T width) { right = left + width; }
void Height(T height) { bottom = top + height; }
Point<T> MidPoint() const
{
return Point<T>((left + right) / 2, (top + bottom) / 2);
}
Path<T> AsPath() const
{
Path<T> result;
result.reserve(4);
result.push_back(Point<T>(left, top));
result.push_back(Point<T>(right, top));
result.push_back(Point<T>(right, bottom));
result.push_back(Point<T>(left, bottom));
return result;
}
bool Contains(const Point<T>& pt) const
{
return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom;
}
bool Contains(const Rect<T>& rec) const
{
return rec.left >= left && rec.right <= right &&
rec.top >= top && rec.bottom <= bottom;
}
void Scale(double scale) {
left *= scale;
top *= scale;
right *= scale;
bottom *= scale;
}
bool IsEmpty() const { return bottom <= top || right <= left; };
bool Intersects(const Rect<T>& rec) const
{
return ((std::max)(left, rec.left) < (std::min)(right, rec.right)) &&
((std::max)(top, rec.top) < (std::min)(bottom, rec.bottom));
};
friend std::ostream& operator<<(std::ostream& os, const Rect<T>& rect) {
os << "("
<< rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom
<< ")";
return os;
}
};
template <typename T1, typename T2>
inline Rect<T1> ScaleRect(const Rect<T2>& rect, double scale)
{
Rect<T1> result;
if constexpr (std::numeric_limits<T1>::is_integer &&
!std::numeric_limits<T2>::is_integer)
{
result.left = static_cast<T1>(std::round(rect.left * scale));
result.top = static_cast<T1>(std::round(rect.top * scale));
result.right = static_cast<T1>(std::round(rect.right * scale));
result.bottom = static_cast<T1>(std::round(rect.bottom * scale));
}
else
{
result.left = rect.left * scale;
result.top = rect.top * scale;
result.right = rect.right * scale;
result.bottom = rect.bottom * scale;
}
return result;
}
static const Rect64 MaxInvalidRect64 = Rect64(
INT64_MAX, INT64_MAX, INT64_MIN, INT64_MIN);
static const RectD MaxInvalidRectD = RectD(
MAX_DBL, MAX_DBL, MIN_DBL, MIN_DBL);
inline Rect64 Bounds(const Path64& path)
{
Rect64 rec = MaxInvalidRect64;
for (const Point64& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return Rect64();
return rec;
}
inline Rect64 Bounds(const Paths64& paths)
{
Rect64 rec = MaxInvalidRect64;
for (const Path64& path : paths)
for (const Point64& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return Rect64();
return rec;
}
inline RectD Bounds(const PathD& path)
{
RectD rec = MaxInvalidRectD;
for (const PointD& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return RectD();
return rec;
}
inline RectD Bounds(const PathsD& paths)
{
RectD rec = MaxInvalidRectD;
for (const PathD& path : paths)
for (const PointD& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return RectD();
return rec;
}
template <typename T> template <typename T>
std::ostream& operator << (std::ostream& outstream, const Path<T>& path) std::ostream& operator << (std::ostream& outstream, const Path<T>& path)
{ {
@ -191,6 +379,7 @@ namespace Clipper2Lib
return outstream; return outstream;
} }
template <typename T1, typename T2> template <typename T1, typename T2>
inline Path<T1> ScalePath(const Path<T2>& path, double scale_x, double scale_y) inline Path<T1> ScalePath(const Path<T2>& path, double scale_x, double scale_y)
{ {
@ -218,6 +407,24 @@ namespace Clipper2Lib
inline Paths<T1> ScalePaths(const Paths<T2>& paths, double scale_x, double scale_y) inline Paths<T1> ScalePaths(const Paths<T2>& paths, double scale_x, double scale_y)
{ {
Paths<T1> result; Paths<T1> result;
if constexpr (std::numeric_limits<T1>::is_integer &&
!std::numeric_limits<T2>::is_integer)
{
RectD r = Bounds(paths);
const double max_coord_d = static_cast<double>(MAX_COORD);
const double min_coord_d = static_cast<double>(MIN_COORD);
if ((r.left * scale_x) < min_coord_d ||
(r.right * scale_x) > max_coord_d ||
(r.top * scale_y) < min_coord_d ||
(r.bottom * scale_y) > max_coord_d)
#ifdef __cpp_exceptions
throw Clipper2Exception(range_error);
#else
return result;
#endif
}
result.reserve(paths.size()); result.reserve(paths.size());
std::transform(paths.begin(), paths.end(), back_inserter(result), std::transform(paths.begin(), paths.end(), back_inserter(result),
[scale_x, scale_y](const auto& path) [scale_x, scale_y](const auto& path)
@ -356,129 +563,6 @@ namespace Clipper2Lib
return result; return result;
} }
// Rect ------------------------------------------------------------------------
template <typename T>
struct Rect;
using Rect64 = Rect<int64_t>;
using RectD = Rect<double>;
template <typename T>
struct Rect {
T left;
T top;
T right;
T bottom;
Rect() :
left(0),
top(0),
right(0),
bottom(0) {}
Rect(T l, T t, T r, T b) :
left(l),
top(t),
right(r),
bottom(b) {}
T Width() const { return right - left; }
T Height() const { return bottom - top; }
void Width(T width) { right = left + width; }
void Height(T height) { bottom = top + height; }
Point<T> MidPoint() const
{
return Point<T>((left + right) / 2, (top + bottom) / 2);
}
Path<T> AsPath() const
{
Path<T> result;
result.reserve(4);
result.push_back(Point<T>(left, top));
result.push_back(Point<T>(right, top));
result.push_back(Point<T>(right, bottom));
result.push_back(Point<T>(left, bottom));
return result;
}
bool Contains(const Point<T>& pt) const
{
return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom;
}
bool Contains(const Rect<T>& rec) const
{
return rec.left >= left && rec.right <= right &&
rec.top >= top && rec.bottom <= bottom;
}
void Scale(double scale) {
left *= scale;
top *= scale;
right *= scale;
bottom *= scale;
}
bool IsEmpty() const { return bottom <= top || right <= left; };
bool Intersects(const Rect<T>& rec) const
{
// nb: if you get compile errors here, then it's almost certainly
// due to including windows.h before including this header.
// To fix this, add #define NOMINMAX just above where you
// #include <windows.h> in you own code.
return (std::max(left, rec.left) < std::min(right, rec.right)) &&
(std::max(top, rec.top) < std::min(bottom, rec.bottom));
};
friend std::ostream& operator<<(std::ostream& os, const Rect<T>& rect) {
os << "("
<< rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom
<< ")";
return os;
}
};
template <typename T1, typename T2>
inline Rect<T1> ScaleRect(const Rect<T2>& rect, double scale)
{
Rect<T1> result;
if constexpr (std::numeric_limits<T1>::is_integer &&
!std::numeric_limits<T2>::is_integer)
{
result.left = static_cast<T1>(std::round(rect.left * scale));
result.top = static_cast<T1>(std::round(rect.top * scale));
result.right = static_cast<T1>(std::round(rect.right * scale));
result.bottom = static_cast<T1>(std::round(rect.bottom * scale));
}
else
{
result.left = rect.left * scale;
result.top = rect.top * scale;
result.right = rect.right * scale;
result.bottom = rect.bottom * scale;
}
return result;
}
// clipper2Exception ---------------------------------------------------------
#ifdef __cpp_exceptions
class Clipper2Exception : public std::exception {
public:
explicit Clipper2Exception(const char* description) :
m_descr(description) {}
virtual const char* what() const throw() { return m_descr.c_str(); }
private:
std::string m_descr;
};
#endif
// Miscellaneous ------------------------------------------------------------ // Miscellaneous ------------------------------------------------------------
inline void CheckPrecision(int& precision) inline void CheckPrecision(int& precision)

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 23 January 2023 * * Date : 27 January 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 *
@ -2288,7 +2288,7 @@ namespace Clipper2Lib {
node.edge1->curr_x = node.pt.x; node.edge1->curr_x = node.pt.x;
node.edge2->curr_x = node.pt.x; node.edge2->curr_x = node.pt.x;
CheckJoinLeft(*node.edge2, node.pt); CheckJoinLeft(*node.edge2, node.pt);
CheckJoinRight(*node.edge1, node.pt); CheckJoinRight(*node.edge1, node.pt, true);
} }
} }
@ -2648,42 +2648,45 @@ namespace Clipper2Lib {
void ClipperBase::CheckJoinLeft(Active& e, const Point64& pt) void ClipperBase::CheckJoinLeft(Active& e, const Point64& pt)
{ {
if (IsOpen(e) || !IsHotEdge(e) || Active* prev = e.prev_in_ael;
!e.prev_in_ael || IsOpen(*e.prev_in_ael) || if (IsOpen(e) || !IsHotEdge(e) || !prev || IsOpen(*prev) ||
!IsHotEdge(*e.prev_in_ael) || e.curr_x != e.prev_in_ael->curr_x || !IsHotEdge(*prev) || e.curr_x != prev->curr_x ||
pt.y <= e.top.y || pt.y <= e.prev_in_ael->top.y || pt.y <= e.top.y || pt.y <= prev->top.y ||
IsJoined(e) || IsOpen(e) || IsJoined(e) || IsOpen(e) ||
CrossProduct(e.top, pt, e.prev_in_ael->top)) CrossProduct(e.top, pt, prev->top))
return; return;
if (e.outrec->idx == e.prev_in_ael->outrec->idx) if (e.outrec->idx == prev->outrec->idx)
AddLocalMaxPoly(*e.prev_in_ael, e, pt); AddLocalMaxPoly(*prev, e, pt);
else if (e.outrec->idx < e.prev_in_ael->outrec->idx) else if (e.outrec->idx < prev->outrec->idx)
JoinOutrecPaths(e, *e.prev_in_ael); JoinOutrecPaths(e, *prev);
else else
JoinOutrecPaths(*e.prev_in_ael, e); JoinOutrecPaths(*prev, e);
e.prev_in_ael->join_with = JoinWith::Right; prev->join_with = JoinWith::Right;
e.join_with = JoinWith::Left; e.join_with = JoinWith::Left;
} }
void ClipperBase::CheckJoinRight(Active& e, const Point64& pt) void ClipperBase::CheckJoinRight(Active& e,
const Point64& pt, bool check_curr_x)
{ {
if (IsOpen(e) || !IsHotEdge(e) || Active* next = e.next_in_ael;
!e.next_in_ael || IsOpen(*e.next_in_ael) || if (IsOpen(e) || !IsHotEdge(e) || IsJoined(e) ||
!IsHotEdge(*e.next_in_ael) || e.curr_x != e.next_in_ael->curr_x || !next || IsOpen(*next) || !IsHotEdge(*next) ||
pt.y <= e.top.y || pt.y <= e.next_in_ael->top.y || pt.y < e.top.y +2 || pt.y < next->top.y +2) // avoids trivial joins
IsJoined(e) || IsOpen(e) ||
CrossProduct(e.top, pt, e.next_in_ael->top))
return; return;
if (e.outrec->idx == e.next_in_ael->outrec->idx) if (check_curr_x) next->curr_x = TopX(*next, pt.y);
AddLocalMaxPoly(e, *e.next_in_ael, pt); if (e.curr_x != next->curr_x ||
else if (e.outrec->idx < e.next_in_ael->outrec->idx) CrossProduct(e.top, pt, next->top)) return;
JoinOutrecPaths(e, *e.next_in_ael);
if (e.outrec->idx == next->outrec->idx)
AddLocalMaxPoly(e, *next, pt);
else if (e.outrec->idx < next->outrec->idx)
JoinOutrecPaths(e, *next);
else else
JoinOutrecPaths(*e.next_in_ael, e); JoinOutrecPaths(*next, e);
e.join_with = JoinWith::Right; e.join_with = JoinWith::Right;
e.next_in_ael->join_with = JoinWith::Left; next->join_with = JoinWith::Left;
} }
inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2) inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2)

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 23 January 2023 * * Date : 27 January 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 *
@ -245,7 +245,8 @@ namespace Clipper2Lib {
void Split(Active& e, const Point64& pt); void Split(Active& e, const Point64& pt);
void CheckJoinLeft(Active& e, const Point64& pt); void CheckJoinLeft(Active& e, const Point64& pt);
void CheckJoinRight(Active& e, const Point64& pt); void CheckJoinRight(Active& e,
const Point64& pt, bool check_curr_x = false);
protected: protected:
bool has_open_paths_ = false; bool has_open_paths_ = false;
bool succeeded_ = true; bool succeeded_ = true;

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 23 January 2023 * * Date : 27 January 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 *
@ -21,18 +21,6 @@
namespace Clipper2Lib { namespace Clipper2Lib {
static const Rect64 MaxInvalidRect64 = Rect64(
(std::numeric_limits<int64_t>::max)(),
(std::numeric_limits<int64_t>::max)(),
(std::numeric_limits<int64_t>::lowest)(),
(std::numeric_limits<int64_t>::lowest)());
static const RectD MaxInvalidRectD = RectD(
(std::numeric_limits<double>::max)(),
(std::numeric_limits<double>::max)(),
(std::numeric_limits<double>::lowest)(),
(std::numeric_limits<double>::lowest)());
inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule, inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
const Paths64& subjects, const Paths64& clips) const Paths64& subjects, const Paths64& clips)
{ {
@ -192,64 +180,6 @@ namespace Clipper2Lib {
return result; return result;
} }
inline Rect64 Bounds(const Path64& path)
{
Rect64 rec = MaxInvalidRect64;
for (const Point64& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return Rect64();
return rec;
}
inline Rect64 Bounds(const Paths64& paths)
{
Rect64 rec = MaxInvalidRect64;
for (const Path64& path : paths)
for (const Point64& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return Rect64();
return rec;
}
inline RectD Bounds(const PathD& path)
{
RectD rec = MaxInvalidRectD;
for (const PointD& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return RectD();
return rec;
}
inline RectD Bounds(const PathsD& paths)
{
RectD rec = MaxInvalidRectD;
for (const PathD& path : paths)
for (const PointD& pt : path)
{
if (pt.x < rec.left) rec.left = pt.x;
if (pt.x > rec.right) rec.right = pt.x;
if (pt.y < rec.top) rec.top = pt.y;
if (pt.y > rec.bottom) rec.bottom = pt.y;
}
if (rec.IsEmpty()) return RectD();
return rec;
}
inline Path64 RectClip(const Rect64& rect, const Path64& path) inline Path64 RectClip(const Rect64& rect, const Path64& path)
{ {
if (rect.IsEmpty() || path.empty()) return Path64(); if (rect.IsEmpty() || path.empty()) return Path64();
@ -552,7 +482,7 @@ namespace Clipper2Lib {
{ {
if (count) if (count)
os << preamble << "+- Polygon with " << count << os << preamble << "+- Polygon with " << count <<
" nested hole" << plural << std::endl; " hole" << plural << std::endl;
else else
os << preamble << "+- Polygon" << std::endl; os << preamble << "+- Polygon" << std::endl;
} }
@ -799,6 +729,108 @@ namespace Clipper2Lib {
return Sqr(a * d - c * b) / (c * c + d * d); return Sqr(a * d - c * b) / (c * c + d * d);
} }
inline size_t GetNext(size_t current, size_t high,
const std::vector<bool>& flags)
{
++current;
while (current <= high && flags[current]) ++current;
if (current <= high) return current;
current = 0;
while (flags[current]) ++current;
return current;
}
inline size_t GetPrior(size_t current, size_t high,
const std::vector<bool>& flags)
{
if (current == 0) current = high;
else --current;
while (current > 0 && flags[current]) --current;
if (!flags[current]) return current;
current = high;
while (flags[current]) --current;
return current;
}
template <typename T>
inline Path<T> SimplifyPath(const Path<T> path,
double epsilon, bool isOpenPath = false)
{
const size_t len = path.size(), high = len -1;
const double epsSqr = Sqr(epsilon);
if (len < 4) return Path<T>(path);
std::vector<bool> flags(len);
std::vector<double> distSqr(len);
size_t prior = high, curr = 0, start, next, prior2, next2;
if (isOpenPath)
{
distSqr[0] = MAX_DBL;
distSqr[high] = MAX_DBL;
}
else
{
distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
}
for (size_t i = 1; i < high; ++i)
distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
for (;;)
{
if (distSqr[curr] > epsSqr)
{
start = curr;
do
{
curr = GetNext(curr, high, flags);
} while (curr != start && distSqr[curr] > epsSqr);
if (curr == start) break;
}
prior = GetPrior(curr, high, flags);
next = GetNext(curr, high, flags);
if (next == prior) break;
if (distSqr[next] < distSqr[curr])
{
flags[next] = true;
next = GetNext(next, high, flags);
next2 = GetNext(next, high, flags);
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
if (next != high || !isOpenPath)
distSqr[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]);
curr = next;
}
else
{
flags[curr] = true;
curr = next;
next = GetNext(next, high, flags);
prior2 = GetPrior(prior, high, flags);
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
if (prior != 0 || !isOpenPath)
distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]);
}
}
Path<T> result;
result.reserve(len);
for (typename Path<T>::size_type i = 0; i < len; ++i)
if (!flags[i]) result.push_back(path[i]);
return result;
}
template <typename T>
inline Paths<T> SimplifyPaths(const Paths<T> paths,
double epsilon, bool isOpenPath = false)
{
Paths<T> result;
result.reserve(paths.size());
for (const auto& path : paths)
result.push_back(SimplifyPath(path, epsilon, isOpenPath));
return result;
}
template <typename T> template <typename T>
inline void RDP(const Path<T> path, std::size_t begin, inline void RDP(const Path<T> path, std::size_t begin,
std::size_t end, double epsSqrd, std::vector<bool>& flags) std::size_t end, double epsSqrd, std::vector<bool>& flags)

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 21 January 2023 * * Date : 25 January 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) *
@ -20,20 +20,39 @@ const double floating_point_tolerance = 1e-12;
// Miscellaneous methods // Miscellaneous methods
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
Paths64::size_type GetLowestPolygonIdx(const Paths64& paths) void GetBoundsAndLowestPolyIdx(const Paths64& paths, Rect64& r, int & idx)
{ {
Paths64::size_type result = 0; idx = -1;
Point64 lp = Point64(static_cast<int64_t>(0), r = MaxInvalidRect64;
std::numeric_limits<int64_t>::min()); int64_t lpx = 0;
for (int i = 0; i < static_cast<int>(paths.size()); ++i)
for (Paths64::size_type i = 0 ; i < paths.size(); ++i)
for (const Point64& p : paths[i]) for (const Point64& p : paths[i])
{ {
if (p.y < lp.y || (p.y == lp.y && p.x >= lp.x)) continue; if (p.y >= r.bottom)
result = i; {
lp = p; if (p.y > r.bottom || p.x < lpx)
{
idx = i;
lpx = p.x;
r.bottom = p.y;
}
}
else if (p.y < r.top) r.top = p.y;
if (p.x > r.right) r.right = p.x;
else if (p.x < r.left) r.left = p.x;
} }
return result; if (idx < 0) r = Rect64(0, 0, 0, 0);
if (r.top == INT64_MIN) r.bottom = r.top;
if (r.left == INT64_MIN) r.left = r.right;
}
bool IsSafeOffset(const Rect64& r, int64_t delta)
{
if (delta < 0) return true;
return r.left > INT64_MIN + delta &&
r.right < INT64_MAX - delta &&
r.top > INT64_MIN + delta &&
r.bottom < INT64_MAX - delta;
} }
PointD GetUnitNormal(const Point64& pt1, const Point64& pt2) PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
@ -356,13 +375,17 @@ void ClipperOffset::DoGroupOffset(Group& group, double delta)
if (group.end_type_ != EndType::Polygon) delta = std::abs(delta) * 0.5; if (group.end_type_ != EndType::Polygon) delta = std::abs(delta) * 0.5;
bool isClosedPaths = IsClosedPath(group.end_type_); bool isClosedPaths = IsClosedPath(group.end_type_);
//the lowermost polygon must be an outer polygon. So we can use that as the
//designated orientation for outer polygons (needed for tidy-up clipping)
Rect64 r;
int idx = 0;
GetBoundsAndLowestPolyIdx(group.paths_in_, r, idx);
if (!IsSafeOffset(r, static_cast<int64_t>(std::ceil(delta))))
throw "Range error - the offset delta is too large";
if (isClosedPaths) if (isClosedPaths)
{ {
//the lowermost polygon must be an outer polygon. So we can use that as the double area = Area(group.paths_in_[idx]);
//designated orientation for outer polygons (needed for tidy-up clipping)
Paths64::size_type lowestIdx = GetLowestPolygonIdx(group.paths_in_);
// nb: don't use the default orientation here ...
double area = Area(group.paths_in_[lowestIdx]);
if (area == 0) return; if (area == 0) return;
group.is_reversed_ = (area < 0); group.is_reversed_ = (area < 0);
if (group.is_reversed_) delta = -delta; if (group.is_reversed_) delta = -delta;
@ -403,7 +426,7 @@ void ClipperOffset::DoGroupOffset(Group& group, double delta)
else else
{ {
int d = (int)std::ceil(abs_group_delta_); int d = (int)std::ceil(abs_group_delta_);
Rect64 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();
} }
group.paths_out_.push_back(group.path_); group.paths_out_.push_back(group.path_);
@ -416,21 +439,6 @@ void ClipperOffset::DoGroupOffset(Group& group, double delta)
else OffsetOpenPath(group, path, group.end_type_); else OffsetOpenPath(group, path, group.end_type_);
} }
} }
if (!merge_groups_)
{
//clean up self-intersections ...
Clipper64 c;
c.PreserveCollinear = false;
//the solution should retain the orientation of the input
c.ReverseSolution = reverse_solution_ != group.is_reversed_;
c.AddSubject(group.paths_out_);
if (group.is_reversed_)
c.Execute(ClipType::Union, FillRule::Negative, group.paths_out_);
else
c.Execute(ClipType::Union, FillRule::Positive, group.paths_out_);
}
solution.reserve(solution.size() + group.paths_out_.size()); solution.reserve(solution.size() + group.paths_out_.size());
copy(group.paths_out_.begin(), group.paths_out_.end(), back_inserter(solution)); copy(group.paths_out_.begin(), group.paths_out_.end(), back_inserter(solution));
group.paths_out_.clear(); group.paths_out_.clear();
@ -439,6 +447,8 @@ void ClipperOffset::DoGroupOffset(Group& group, double delta)
Paths64 ClipperOffset::Execute(double delta) Paths64 ClipperOffset::Execute(double delta)
{ {
solution.clear(); solution.clear();
if (groups_.size() == 0) return solution;
if (std::abs(delta) < default_arc_tolerance) if (std::abs(delta) < default_arc_tolerance)
{ {
for (const Group& group : groups_) for (const Group& group : groups_)
@ -453,26 +463,21 @@ Paths64 ClipperOffset::Execute(double delta)
2.0 : 2.0 :
2.0 / (miter_limit_ * miter_limit_); 2.0 / (miter_limit_ * miter_limit_);
std::vector<Group>::iterator groups_iter; std::vector<Group>::iterator git;
for (groups_iter = groups_.begin(); for (git = groups_.begin(); git != groups_.end(); ++git)
groups_iter != groups_.end(); ++groups_iter) DoGroupOffset(*git, delta);
{
DoGroupOffset(*groups_iter, delta); //clean up self-intersections ...
} Clipper64 c;
c.PreserveCollinear = false;
//the solution should retain the orientation of the input
c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed_;
c.AddSubject(solution);
if (groups_[0].is_reversed_)
c.Execute(ClipType::Union, FillRule::Negative, solution);
else
c.Execute(ClipType::Union, FillRule::Positive, solution);
if (merge_groups_ && groups_.size() > 0)
{
//clean up self-intersections ...
Clipper64 c;
c.PreserveCollinear = false;
//the solution should retain the orientation of the input
c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed_;
c.AddSubject(solution);
if (groups_[0].is_reversed_)
c.Execute(ClipType::Union, FillRule::Negative, solution);
else
c.Execute(ClipType::Union, FillRule::Positive, solution);
}
return solution; return solution;
} }

View File

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 21 January 2023 * * Date : 25 January 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) *
@ -49,7 +49,6 @@ private:
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;
@ -89,14 +88,6 @@ 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;}