summaryrefslogtreecommitdiffstats
path: root/thirdparty/clipper2/src/clipper.offset.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'thirdparty/clipper2/src/clipper.offset.cpp')
-rw-r--r--thirdparty/clipper2/src/clipper.offset.cpp561
1 files changed, 333 insertions, 228 deletions
diff --git a/thirdparty/clipper2/src/clipper.offset.cpp b/thirdparty/clipper2/src/clipper.offset.cpp
index 78cd82376a..0282aa49bb 100644
--- a/thirdparty/clipper2/src/clipper.offset.cpp
+++ b/thirdparty/clipper2/src/clipper.offset.cpp
@@ -1,6 +1,6 @@
/*******************************************************************************
* Author : Angus Johnson *
-* Date : 22 March 2023 *
+* Date : 28 November 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : Path Offset (Inflate/Shrink) *
@@ -20,38 +20,63 @@ const double floating_point_tolerance = 1e-12;
// Miscellaneous methods
//------------------------------------------------------------------------------
-void GetBoundsAndLowestPolyIdx(const Paths64& paths, Rect64& r, int & idx)
+inline bool ToggleBoolIf(bool val, bool condition)
{
- idx = -1;
- r = MaxInvalidRect64;
- int64_t lpx = 0;
- for (int i = 0; i < static_cast<int>(paths.size()); ++i)
- for (const Point64& p : paths[i])
+ return condition ? !val : val;
+}
+
+void GetMultiBounds(const Paths64& paths, std::vector<Rect64>& recList)
+{
+ recList.reserve(paths.size());
+ for (const Path64& path : paths)
+ {
+ if (path.size() < 1)
{
- if (p.y >= r.bottom)
- {
- if (p.y > r.bottom || p.x < lpx)
- {
- idx = i;
- lpx = p.x;
- r.bottom = p.y;
- }
- }
- else if (p.y < r.top) r.top = p.y;
- if (p.x > r.right) r.right = p.x;
- else if (p.x < r.left) r.left = p.x;
+ recList.push_back(InvalidRect64);
+ continue;
+ }
+ int64_t x = path[0].x, y = path[0].y;
+ Rect64 r = Rect64(x, y, x, y);
+ for (const Point64& pt : path)
+ {
+ if (pt.y > r.bottom) r.bottom = pt.y;
+ else if (pt.y < r.top) r.top = pt.y;
+ if (pt.x > r.right) r.right = pt.x;
+ else if (pt.x < r.left) r.left = pt.x;
}
- //if (idx < 0) r = Rect64(0, 0, 0, 0);
- //if (r.top == INT64_MIN) r.bottom = r.top;
- //if (r.left == INT64_MIN) r.left = r.right;
+ recList.push_back(r);
+ }
}
-bool IsSafeOffset(const Rect64& r, double abs_delta)
+bool ValidateBounds(std::vector<Rect64>& recList, double delta)
{
- return r.left > min_coord + abs_delta &&
- r.right < max_coord - abs_delta &&
- r.top > min_coord + abs_delta &&
- r.bottom < max_coord - abs_delta;
+ int64_t int_delta = static_cast<int64_t>(delta);
+ int64_t big = MAX_COORD - int_delta;
+ int64_t small = MIN_COORD + int_delta;
+ for (const Rect64& r : recList)
+ {
+ if (!r.IsValid()) continue; // ignore invalid paths
+ else if (r.left < small || r.right > big ||
+ r.top < small || r.bottom > big) return false;
+ }
+ return true;
+}
+
+int GetLowestClosedPathIdx(std::vector<Rect64>& boundsList)
+{
+ int i = -1, result = -1;
+ Point64 botPt = Point64(INT64_MAX, INT64_MIN);
+ for (const Rect64& r : boundsList)
+ {
+ ++i;
+ if (!r.IsValid()) continue; // ignore invalid paths
+ else if (r.bottom > botPt.y || (r.bottom == botPt.y && r.left < botPt.x))
+ {
+ botPt = Point64(r.left, r.bottom);
+ result = static_cast<int>(i);
+ }
+ }
+ return result;
}
PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
@@ -78,8 +103,7 @@ inline double Hypot(double x, double y)
}
inline PointD NormalizeVector(const PointD& vec)
-{
-
+{
double h = Hypot(vec.x, vec.y);
if (AlmostZero(h)) return PointD(0,0);
double inverseHypot = 1 / h;
@@ -126,6 +150,44 @@ inline void NegatePath(PathD& path)
}
}
+
+//------------------------------------------------------------------------------
+// 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);
+
+ // get bounds of each path --> bounds_list
+ GetMultiBounds(paths_in, bounds_list);
+
+ if (end_type == EndType::Polygon)
+ {
+ is_hole_list.reserve(paths_in.size());
+ for (const Path64& path : paths_in)
+ is_hole_list.push_back(Area(path) < 0);
+ lowest_path_idx = GetLowestClosedPathIdx(bounds_list);
+ // 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 >= 0) && is_hole_list[lowest_path_idx];
+ if (is_reversed) is_hole_list.flip();
+ }
+ else
+ {
+ lowest_path_idx = -1;
+ is_reversed = false;
+ is_hole_list.resize(paths_in.size());
+ }
+}
+
+
//------------------------------------------------------------------------------
// ClipperOffset methods
//------------------------------------------------------------------------------
@@ -148,10 +210,10 @@ void ClipperOffset::BuildNormals(const Path64& path)
norms.clear();
norms.reserve(path.size());
if (path.size() == 0) return;
- Path64::const_iterator path_iter, path_last_iter = --path.cend();
- for (path_iter = path.cbegin(); path_iter != path_last_iter; ++path_iter)
+ Path64::const_iterator path_iter, path_stop_iter = --path.cend();
+ for (path_iter = path.cbegin(); path_iter != path_stop_iter; ++path_iter)
norms.push_back(GetUnitNormal(*path_iter,*(path_iter +1)));
- norms.push_back(GetUnitNormal(*path_last_iter, *(path.cbegin())));
+ norms.push_back(GetUnitNormal(*path_stop_iter, *(path.cbegin())));
}
inline PointD TranslatePoint(const PointD& pt, double dx, double dy)
@@ -201,19 +263,39 @@ PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b,
}
}
-void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k)
+void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k)
+{
+ PointD pt1, pt2;
+ if (j == k)
+ {
+ double abs_delta = std::abs(group_delta_);
+ pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y);
+ pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y);
+ }
+ else
+ {
+ pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y);
+ pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y);
+ }
+ path_out.push_back(Point64(pt1));
+ path_out.push_back(Point64(pt2));
+}
+
+void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
{
PointD vec;
if (j == k)
- vec = PointD(norms[0].y, -norms[0].x);
+ vec = PointD(norms[j].y, -norms[j].x);
else
vec = GetAvgUnitVector(
PointD(-norms[k].y, norms[k].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
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
PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x);
PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x);
@@ -227,8 +309,8 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t
pt.z = ptQ.z;
#endif
//get the second intersect point through reflecion
- group.path.push_back(Point64(ReflectPoint(pt, ptQ)));
- group.path.push_back(Point64(pt));
+ path_out.push_back(Point64(ReflectPoint(pt, ptQ)));
+ path_out.push_back(Point64(pt));
}
else
{
@@ -237,57 +319,67 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t
#ifdef USINGZ
pt.z = ptQ.z;
#endif
- group.path.push_back(Point64(pt));
+ path_out.push_back(Point64(pt));
//get the second intersect point through reflecion
- group.path.push_back(Point64(ReflectPoint(pt, ptQ)));
+ path_out.push_back(Point64(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);
#ifdef USINGZ
- group.path.push_back(Point64(
+ path_out.push_back(Point64(
path[j].x + (norms[k].x + norms[j].x) * q,
path[j].y + (norms[k].y + norms[j].y) * q,
path[j].z));
#else
- group.path.push_back(Point64(
+ path_out.push_back(Point64(
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)
{
+ 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];
PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
if (j == k) offsetVec.Negate();
#ifdef USINGZ
- group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
+ path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
#else
- group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
+ path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
#endif
- if (angle > -PI + 0.01) // avoid 180deg concave
+ 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
{
- int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
- for (int i = 1; i < steps; ++i) // ie 1 less than steps
- {
- offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
- offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
+ offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
+ offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
#ifdef USINGZ
- group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
+ path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
#else
- group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
+ path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
#endif
-
- }
}
- group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_));
+ path_out.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, const Path64& path, size_t j, size_t k)
{
// Let A = change in angle where edges join
// A == 0: ie no change in angle (flat join)
@@ -302,50 +394,57 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k)
if (sin_a > 1.0) sin_a = 1.0;
else if (sin_a < -1.0) sin_a = -1.0;
- if (cos_a > 0.99) // almost straight - less than 8 degrees
+ if (deltaCallback64_) {
+ group_delta_ = deltaCallback64_(path, norms, j, k);
+ if (group.is_reversed) group_delta_ = -group_delta_;
+ }
+ if (std::fabs(group_delta_) <= floating_point_tolerance)
{
- group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_));
- if (cos_a < 0.9998) // greater than 1 degree (#424)
- group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); // (#418)
+ path_out.push_back(path[j]);
+ return;
}
- else if (cos_a > -0.99 && (sin_a * group_delta_ < 0))
+
+ if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
{
// is concave
- group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_));
+ path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_));
// this extra point is the only (simple) way to ensure that
- // path reversals are fully cleaned with the trailing clipper
- group.path.push_back(path[j]); // (#405)
- group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_));
- }
- else if (join_type_ == JoinType::Round)
- DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
+ // path reversals are fully cleaned with the trailing clipper
+ path_out.push_back(path[j]); // (#405)
+ path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_));
+ }
+ else if (cos_a > 0.999 && join_type_ != JoinType::Round)
+ {
+ // almost straight - less than 2.5 degree (#424, #482, #526 & #724)
+ DoMiter(path, j, k, cos_a);
+ }
else if (join_type_ == JoinType::Miter)
{
- // miter unless the angle is so acute the miter would exceeds ML
- if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
- else DoSquare(group, path, j, k);
+ // miter unless the angle is sufficiently acute to exceed ML
+ if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a);
+ else DoSquare(path, j, k);
}
- // don't bother squaring angles that deviate < ~20 degrees because
- // squaring will be indistinguishable from mitering and just be a lot slower
- else if (cos_a > 0.9)
- DoMiter(group, path, j, k, cos_a);
+ else if (join_type_ == JoinType::Round)
+ DoRound(path, j, k, std::atan2(sin_a, cos_a));
+ else if ( join_type_ == JoinType::Bevel)
+ DoBevel(path, j, k);
else
- DoSquare(group, path, j, k);
-
- k = j;
+ DoSquare(path, j, k);
}
-void ClipperOffset::OffsetPolygon(Group& group, Path64& path)
+void ClipperOffset::OffsetPolygon(Group& group, const Path64& path)
{
- for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i)
- OffsetPoint(group, path, i, j);
- group.paths_out.push_back(group.path);
+ path_out.clear();
+ for (Path64::size_type j = 0, k = path.size() -1; j < path.size(); k = j, ++j)
+ OffsetPoint(group, path, j, k);
+ solution.push_back(path_out);
}
-void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path)
+void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
{
OffsetPolygon(group, path);
- std::reverse(path.begin(), path.end());
+ Path64 reverse_path(path);
+ std::reverse(reverse_path.begin(), reverse_path.end());
//rebuild normals // BuildNormals(path);
std::reverse(norms.begin(), norms.end());
@@ -353,41 +452,36 @@ void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path)
norms.erase(norms.begin());
NegatePath(norms);
- group.path.clear();
- OffsetPolygon(group, path);
+ OffsetPolygon(group, reverse_path);
}
-void ClipperOffset::OffsetOpenPath(Group& group, Path64& path)
+void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
{
// 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.push_back(path[0]);
+ else
{
- case EndType::Butt:
-#ifdef USINGZ
- group.path.push_back(Point64(
- path[0].x - norms[0].x * group_delta_,
- path[0].y - norms[0].y * group_delta_,
- path[0].z));
-#else
- group.path.push_back(Point64(
- path[0].x - norms[0].x * group_delta_,
- path[0].y - norms[0].y * group_delta_));
-#endif
- group.path.push_back(GetPerpendic(path[0], norms[0], group_delta_));
- break;
- case EndType::Round:
- DoRound(group, path, 0,0, PI);
- break;
- default:
- DoSquare(group, path, 0, 0);
- break;
+ switch (end_type_)
+ {
+ case EndType::Butt:
+ DoBevel(path, 0, 0);
+ break;
+ case EndType::Round:
+ DoRound(path, 0, 0, PI);
+ break;
+ default:
+ DoSquare(path, 0, 0);
+ break;
+ }
}
-
+
size_t highI = path.size() - 1;
-
// offset the left side going forward
- for (Path64::size_type i = 1, k = 0; i < highI; ++i)
- OffsetPoint(group, path, i, k);
+ for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j)
+ OffsetPoint(group, path, j, k);
// reverse normals
for (size_t i = highI; i > 0; --i)
@@ -395,60 +489,46 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path)
norms[0] = norms[highI];
// 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.push_back(path[highI]);
+ else
{
- case EndType::Butt:
-#ifdef USINGZ
- group.path.push_back(Point64(
- path[highI].x - norms[highI].x * group_delta_,
- path[highI].y - norms[highI].y * group_delta_,
- path[highI].z));
-#else
- group.path.push_back(Point64(
- path[highI].x - norms[highI].x * group_delta_,
- path[highI].y - norms[highI].y * group_delta_));
-#endif
- group.path.push_back(GetPerpendic(path[highI], norms[highI], group_delta_));
- break;
- case EndType::Round:
- DoRound(group, path, highI, highI, PI);
- break;
- default:
- DoSquare(group, path, highI, highI);
- break;
+ switch (end_type_)
+ {
+ case EndType::Butt:
+ DoBevel(path, highI, highI);
+ break;
+ case EndType::Round:
+ DoRound(path, highI, highI, PI);
+ break;
+ default:
+ DoSquare(path, highI, highI);
+ break;
+ }
}
- for (size_t i = highI, k = 0; i > 0; --i)
- OffsetPoint(group, path, i, k);
- group.paths_out.push_back(group.path);
+ for (size_t j = highI, k = 0; j > 0; k = j, --j)
+ OffsetPoint(group, path, j, k);
+ solution.push_back(path_out);
}
void ClipperOffset::DoGroupOffset(Group& group)
{
- Rect64 r;
- int idx = -1;
- //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)
- GetBoundsAndLowestPolyIdx(group.paths_in, r, idx);
- if (idx < 0) return;
-
if (group.end_type == EndType::Polygon)
{
- double area = Area(group.paths_in[idx]);
- //if (area == 0) return; // probably unhelpful (#430)
- group.is_reversed = (area < 0);
- if (group.is_reversed) group_delta_ = -delta_;
- else group_delta_ = delta_;
- }
- else
- {
- group.is_reversed = false;
- group_delta_ = std::abs(delta_) * 0.5;
+ // a straight path (2 points) can now also be 'polygon' offset
+ // where the ends will be treated as (180 deg.) joins
+ if (group.lowest_path_idx < 0) delta_ = std::abs(delta_);
+ group_delta_ = (group.is_reversed) ? -delta_ : delta_;
}
- abs_group_delta_ = std::fabs(group_delta_);
+ else
+ group_delta_ = std::abs(delta_);// *0.5;
- // do range checking
- if (!IsSafeOffset(r, abs_group_delta_))
+ double abs_delta = std::fabs(group_delta_);
+ if (!ValidateBounds(group.bounds_list, abs_delta))
{
DoError(range_error_i);
error_code_ |= range_error_i;
@@ -458,80 +538,98 @@ void ClipperOffset::DoGroupOffset(Group& group)
join_type_ = group.join_type;
end_type_ = group.end_type;
- //calculate a sensible number of steps (for 360 deg for the given offset
if (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
// curve imprecision that's allowed is based on the size of the
// offset (delta). Obviously very large offsets will almost always
// require much less precision. See also offset_triginometry2.svg
double arcTol = (arc_tolerance_ > floating_point_tolerance ?
- std::min(abs_group_delta_, arc_tolerance_) :
- std::log10(2 + abs_group_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
+ 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);
+ if (group_delta_ < 0.0) step_sin_ = -step_sin_;
+ steps_per_rad_ = steps_per_360 / (2 * PI);
}
- bool is_joined =
- (end_type_ == EndType::Polygon) ||
- (end_type_ == EndType::Joined);
- Paths64::const_iterator path_iter;
- for(path_iter = group.paths_in.cbegin(); path_iter != group.paths_in.cend(); ++path_iter)
+ std::vector<Rect64>::const_iterator path_rect_it = group.bounds_list.cbegin();
+ std::vector<bool>::const_iterator is_hole_it = group.is_hole_list.cbegin();
+ Paths64::const_iterator path_in_it = group.paths_in.cbegin();
+ for ( ; path_in_it != group.paths_in.cend(); ++path_in_it, ++path_rect_it, ++is_hole_it)
{
- Path64 path = StripDuplicates(*path_iter, is_joined);
- Path64::size_type cnt = path.size();
- if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon))
- continue;
+ if (!path_rect_it->IsValid()) continue;
+ Path64::size_type pathLen = path_in_it->size();
+ path_out.clear();
- group.path.clear();
- if (cnt == 1) // single point - only valid with open paths
+ if (pathLen == 1) // single point
{
if (group_delta_ < 1) continue;
+ const Point64& pt = (*path_in_it)[0];
//single vertex so build a circle or square ...
if (group.join_type == JoinType::Round)
{
- double radius = abs_group_delta_;
- group.path = Ellipse(path[0], radius, radius);
+ double radius = abs_delta;
+ int steps = static_cast<int>(std::ceil(steps_per_rad_ * 2 * PI)); //#617
+ path_out = Ellipse(pt, radius, radius, steps);
#ifdef USINGZ
- for (auto& p : group.path) p.z = path[0].z;
+ for (auto& p : path_out) p.z = pt.z;
#endif
}
else
{
- int d = (int)std::ceil(abs_group_delta_);
- r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d);
- group.path = r.AsPath();
+ int d = (int)std::ceil(abs_delta);
+ Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d);
+ path_out = r.AsPath();
#ifdef USINGZ
- for (auto& p : group.path) p.z = path[0].z;
+ for (auto& p : path_out) p.z = pt.z;
#endif
}
- group.paths_out.push_back(group.path);
- }
- else
- {
- if ((cnt == 2) && (group.end_type == EndType::Joined))
- {
- if (group.join_type == JoinType::Round)
- end_type_ = EndType::Round;
- else
- end_type_ = EndType::Square;
- }
+ solution.push_back(path_out);
+ continue;
+ } // end of offsetting a single point
+
+ // when shrinking outer paths, make sure they can shrink this far (#593)
+ // also when shrinking holes, make sure they too can shrink this far (#715)
+ if ((group_delta_ > 0) == ToggleBoolIf(*is_hole_it, group.is_reversed) &&
+ (std::min(path_rect_it->Width(), path_rect_it->Height()) <= -group_delta_ * 2) )
+ continue;
+
+ if ((pathLen == 2) && (group.end_type == EndType::Joined))
+ end_type_ = (group.join_type == JoinType::Round) ?
+ 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);
+ }
+}
+
+
+size_t ClipperOffset::CalcSolutionCapacity()
+{
+ size_t result = 0;
+ for (const Group& g : groups_)
+ result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size();
+ return result;
+}
- BuildNormals(path);
- if (end_type_ == EndType::Polygon) OffsetPolygon(group, path);
- else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path);
- else OffsetOpenPath(group, path);
+bool ClipperOffset::CheckReverseOrientation()
+{
+ // nb: this assumes there's consistency in orientation between groups
+ bool is_reversed_orientation = false;
+ for (const Group& g : groups_)
+ if (g.end_type == EndType::Polygon)
+ {
+ is_reversed_orientation = g.is_reversed;
+ break;
}
- }
- solution.reserve(solution.size() + group.paths_out.size());
- copy(group.paths_out.begin(), group.paths_out.end(), back_inserter(solution));
- group.paths_out.clear();
+ return is_reversed_orientation;
}
void ClipperOffset::ExecuteInternal(double delta)
@@ -539,29 +637,29 @@ void ClipperOffset::ExecuteInternal(double delta)
error_code_ = 0;
solution.clear();
if (groups_.size() == 0) return;
+ solution.reserve(CalcSolutionCapacity());
- if (std::abs(delta) < 0.5)
+ 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_)
- {
- solution.reserve(solution.size() + group.paths_in.size());
copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(solution));
- }
- }
- else
- {
- temp_lim_ = (miter_limit_ <= 1) ?
- 2.0 :
- 2.0 / (miter_limit_ * miter_limit_);
+ return;
+ }
- delta_ = delta;
- std::vector<Group>::iterator git;
- for (git = groups_.begin(); git != groups_.end(); ++git)
- {
- DoGroupOffset(*git);
- if (!error_code_) continue; // all OK
- solution.clear();
- }
+ temp_lim_ = (miter_limit_ <= 1) ?
+ 2.0 :
+ 2.0 / (miter_limit_ * miter_limit_);
+
+ delta_ = delta;
+ std::vector<Group>::iterator git;
+ for (git = groups_.begin(); git != groups_.end(); ++git)
+ {
+ DoGroupOffset(*git);
+ if (!error_code_) continue; // all OK
+ solution.clear();
}
}
@@ -572,19 +670,17 @@ void ClipperOffset::Execute(double delta, Paths64& paths)
ExecuteInternal(delta);
if (!solution.size()) return;
- paths = solution;
+ bool paths_reversed = CheckReverseOrientation();
//clean up self-intersections ...
Clipper64 c;
- c.PreserveCollinear = false;
+ c.PreserveCollinear(false);
//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
- if (zCallback64_) {
- c.SetZCallback(zCallback64_);
- }
+ if (zCallback64_) { c.SetZCallback(zCallback64_); }
#endif
c.AddSubject(solution);
- if (groups_[0].is_reversed)
+ if (paths_reversed)
c.Execute(ClipType::Union, FillRule::Negative, paths);
else
c.Execute(ClipType::Union, FillRule::Positive, paths);
@@ -598,21 +694,30 @@ void ClipperOffset::Execute(double delta, PolyTree64& polytree)
ExecuteInternal(delta);
if (!solution.size()) return;
+ bool paths_reversed = CheckReverseOrientation();
//clean up self-intersections ...
Clipper64 c;
- c.PreserveCollinear = false;
+ c.PreserveCollinear(false);
//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
if (zCallback64_) {
c.SetZCallback(zCallback64_);
}
#endif
c.AddSubject(solution);
- if (groups_[0].is_reversed)
+
+
+ if (paths_reversed)
c.Execute(ClipType::Union, FillRule::Negative, polytree);
else
c.Execute(ClipType::Union, FillRule::Positive, polytree);
}
+void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths)
+{
+ deltaCallback64_ = delta_cb;
+ Execute(1.0, paths);
+}
+
} // namespace