generated from hertog/godot-module-template
646 lines
19 KiB
C++
646 lines
19 KiB
C++
/*******************************************************************************
|
|
* Author : Angus Johnson *
|
|
* Date : 17 April 2024 *
|
|
* Website : http://www.angusj.com *
|
|
* Copyright : Angus Johnson 2010-2024 *
|
|
* Purpose : Path Offset (Inflate/Shrink) *
|
|
* License : http://www.boost.org/LICENSE_1_0.txt *
|
|
*******************************************************************************/
|
|
|
|
#include <cmath>
|
|
#include "clipper2/clipper.h"
|
|
#include "clipper2/clipper.offset.h"
|
|
|
|
namespace Clipper2Lib {
|
|
|
|
const double default_arc_tolerance = 0.25;
|
|
const double floating_point_tolerance = 1e-12;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Miscellaneous methods
|
|
//------------------------------------------------------------------------------
|
|
|
|
std::optional<size_t> GetLowestClosedPathIdx(const Paths64& paths)
|
|
{
|
|
std::optional<size_t> result;
|
|
Point64 botPt = Point64(INT64_MAX, INT64_MIN);
|
|
for (size_t i = 0; i < paths.size(); ++i)
|
|
{
|
|
for (const Point64& pt : paths[i])
|
|
{
|
|
if ((pt.y < botPt.y) ||
|
|
((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
|
|
result = i;
|
|
botPt.x = pt.x;
|
|
botPt.y = pt.y;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
|
|
{
|
|
double dx, dy, inverse_hypot;
|
|
if (pt1 == pt2) return PointD(0.0, 0.0);
|
|
dx = static_cast<double>(pt2.x - pt1.x);
|
|
dy = static_cast<double>(pt2.y - pt1.y);
|
|
inverse_hypot = 1.0 / hypot(dx, dy);
|
|
dx *= inverse_hypot;
|
|
dy *= inverse_hypot;
|
|
return PointD(dy, -dx);
|
|
}
|
|
|
|
inline bool AlmostZero(double value, double epsilon = 0.001)
|
|
{
|
|
return std::fabs(value) < epsilon;
|
|
}
|
|
|
|
inline double Hypot(double x, double y)
|
|
{
|
|
//see https://stackoverflow.com/a/32436148/359538
|
|
return std::sqrt(x * x + y * y);
|
|
}
|
|
|
|
inline PointD NormalizeVector(const PointD& vec)
|
|
{
|
|
double h = Hypot(vec.x, vec.y);
|
|
if (AlmostZero(h)) return PointD(0,0);
|
|
double inverseHypot = 1 / h;
|
|
return PointD(vec.x * inverseHypot, vec.y * inverseHypot);
|
|
}
|
|
|
|
inline PointD GetAvgUnitVector(const PointD& vec1, const PointD& vec2)
|
|
{
|
|
return NormalizeVector(PointD(vec1.x + vec2.x, vec1.y + vec2.y));
|
|
}
|
|
|
|
inline bool IsClosedPath(EndType et)
|
|
{
|
|
return et == EndType::Polygon || et == EndType::Joined;
|
|
}
|
|
|
|
inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
|
|
{
|
|
#ifdef USINGZ
|
|
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
|
|
#else
|
|
return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
|
|
#endif
|
|
}
|
|
|
|
inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
|
|
{
|
|
#ifdef USINGZ
|
|
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
|
|
#else
|
|
return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
|
|
#endif
|
|
}
|
|
|
|
inline void NegatePath(PathD& path)
|
|
{
|
|
for (PointD& pt : path)
|
|
{
|
|
pt.x = -pt.x;
|
|
pt.y = -pt.y;
|
|
#ifdef USINGZ
|
|
pt.z = pt.z;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// ClipperOffset::Group methods
|
|
//------------------------------------------------------------------------------
|
|
|
|
ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type):
|
|
paths_in(_paths), join_type(_join_type), end_type(_end_type)
|
|
{
|
|
bool is_joined =
|
|
(end_type == EndType::Polygon) ||
|
|
(end_type == EndType::Joined);
|
|
for (Path64& p: paths_in)
|
|
StripDuplicates(p, is_joined);
|
|
|
|
if (end_type == EndType::Polygon)
|
|
{
|
|
lowest_path_idx = GetLowestClosedPathIdx(paths_in);
|
|
// the lowermost path must be an outer path, so if its orientation is negative,
|
|
// then flag the whole group is 'reversed' (will negate delta etc.)
|
|
// as this is much more efficient than reversing every path.
|
|
is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0;
|
|
}
|
|
else
|
|
{
|
|
lowest_path_idx = std::nullopt;
|
|
is_reversed = false;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// ClipperOffset methods
|
|
//------------------------------------------------------------------------------
|
|
|
|
void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_)
|
|
{
|
|
Paths64 paths;
|
|
paths.push_back(path);
|
|
AddPaths(paths, jt_, et_);
|
|
}
|
|
|
|
void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_)
|
|
{
|
|
if (paths.size() == 0) return;
|
|
groups_.push_back(Group(paths, jt_, et_));
|
|
}
|
|
|
|
void ClipperOffset::BuildNormals(const Path64& path)
|
|
{
|
|
norms.clear();
|
|
norms.reserve(path.size());
|
|
if (path.size() == 0) return;
|
|
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_stop_iter, *(path.cbegin())));
|
|
}
|
|
|
|
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_);
|
|
#ifdef USINGZ
|
|
pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y, path[j].z);
|
|
pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y, path[j].z);
|
|
#else
|
|
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);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef USINGZ
|
|
pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y, path[j].z);
|
|
pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y, path[j].z);
|
|
#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);
|
|
#endif
|
|
}
|
|
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[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_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);
|
|
// get 2 vertices along one edge offset
|
|
PointD pt3 = GetPerpendicD(path[k], norms[k], group_delta_);
|
|
if (j == k)
|
|
{
|
|
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
|
|
PointD pt = ptQ;
|
|
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
|
//get the second intersect point through reflecion
|
|
path_out.push_back(Point64(ReflectPoint(pt, ptQ)));
|
|
path_out.push_back(Point64(pt));
|
|
}
|
|
else
|
|
{
|
|
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
|
|
PointD pt = ptQ;
|
|
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
|
path_out.push_back(Point64(pt));
|
|
//get the second intersect point through reflecion
|
|
path_out.push_back(Point64(ReflectPoint(pt, ptQ)));
|
|
}
|
|
}
|
|
|
|
void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a)
|
|
{
|
|
double q = group_delta_ / (cos_a + 1);
|
|
#ifdef USINGZ
|
|
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
|
|
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(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
|
|
path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
|
|
#else
|
|
path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
|
|
#endif
|
|
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
|
|
for (int i = 1; i < steps; ++i) // ie 1 less than steps
|
|
{
|
|
offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
|
|
offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
|
|
#ifdef USINGZ
|
|
path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
|
|
#else
|
|
path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
|
|
#endif
|
|
}
|
|
path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_));
|
|
}
|
|
|
|
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)
|
|
// A == PI: edges 'spike'
|
|
// sin(A) < 0: right turning
|
|
// cos(A) < 0: change in angle is more than 90 degree
|
|
|
|
if (path[j] == path[k]) return;
|
|
|
|
double sin_a = CrossProduct(norms[j], norms[k]);
|
|
double cos_a = DotProduct(norms[j], norms[k]);
|
|
if (sin_a > 1.0) sin_a = 1.0;
|
|
else if (sin_a < -1.0) sin_a = -1.0;
|
|
|
|
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)
|
|
{
|
|
path_out.push_back(path[j]);
|
|
return;
|
|
}
|
|
|
|
if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
|
|
{
|
|
// is concave (so insert 3 points that will create a negative region)
|
|
#ifdef USINGZ
|
|
path_out.push_back(Point64(GetPerpendic(path[j], norms[k], group_delta_), path[j].z));
|
|
#else
|
|
path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_));
|
|
#endif
|
|
|
|
// this extra point is the only simple way to ensure that path reversals
|
|
// (ie over-shrunk paths) are fully cleaned out with the trailing union op.
|
|
// However it's probably safe to skip this whenever an angle is almost flat.
|
|
if (cos_a < 0.99) path_out.push_back(path[j]); // (#405)
|
|
|
|
#ifdef USINGZ
|
|
path_out.push_back(Point64(GetPerpendic(path[j], norms[j], group_delta_), path[j].z));
|
|
#else
|
|
path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_));
|
|
#endif
|
|
}
|
|
else if (cos_a > 0.999 && join_type_ != JoinType::Round)
|
|
{
|
|
// almost straight - less than 2.5 degree (#424, #482, #526 & #724)
|
|
DoMiter(path, j, k, cos_a);
|
|
}
|
|
else if (join_type_ == JoinType::Miter)
|
|
{
|
|
// miter unless the angle is sufficiently acute to exceed ML
|
|
if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a);
|
|
else DoSquare(path, j, k);
|
|
}
|
|
else if (join_type_ == JoinType::Round)
|
|
DoRound(path, j, k, std::atan2(sin_a, cos_a));
|
|
else if ( join_type_ == JoinType::Bevel)
|
|
DoBevel(path, j, k);
|
|
else
|
|
DoSquare(path, j, k);
|
|
}
|
|
|
|
void ClipperOffset::OffsetPolygon(Group& group, const Path64& 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, const Path64& path)
|
|
{
|
|
OffsetPolygon(group, path);
|
|
Path64 reverse_path(path);
|
|
std::reverse(reverse_path.begin(), reverse_path.end());
|
|
|
|
//rebuild normals
|
|
std::reverse(norms.begin(), norms.end());
|
|
norms.push_back(norms[0]);
|
|
norms.erase(norms.begin());
|
|
NegatePath(norms);
|
|
|
|
OffsetPolygon(group, reverse_path);
|
|
}
|
|
|
|
void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
|
|
{
|
|
// do the line start cap
|
|
if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0);
|
|
|
|
if (std::fabs(group_delta_) <= floating_point_tolerance)
|
|
path_out.push_back(path[0]);
|
|
else
|
|
{
|
|
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 j = 1, k = 0; j < highI; k = j, ++j)
|
|
OffsetPoint(group, path, j, k);
|
|
|
|
// reverse normals
|
|
for (size_t i = highI; i > 0; --i)
|
|
norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
|
|
norms[0] = norms[highI];
|
|
|
|
// do the line end cap
|
|
if (deltaCallback64_)
|
|
group_delta_ = deltaCallback64_(path, norms, highI, highI);
|
|
|
|
if (std::fabs(group_delta_) <= floating_point_tolerance)
|
|
path_out.push_back(path[highI]);
|
|
else
|
|
{
|
|
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 j = highI -1, k = highI; j > 0; k = j, --j)
|
|
OffsetPoint(group, path, j, k);
|
|
solution->push_back(path_out);
|
|
}
|
|
|
|
void ClipperOffset::DoGroupOffset(Group& group)
|
|
{
|
|
if (group.end_type == EndType::Polygon)
|
|
{
|
|
// 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.has_value()) delta_ = std::abs(delta_);
|
|
group_delta_ = (group.is_reversed) ? -delta_ : delta_;
|
|
}
|
|
else
|
|
group_delta_ = std::abs(delta_);// *0.5;
|
|
|
|
double abs_delta = std::fabs(group_delta_);
|
|
join_type_ = group.join_type;
|
|
end_type_ = group.end_type;
|
|
|
|
if (group.join_type == JoinType::Round || group.end_type == EndType::Round)
|
|
{
|
|
// calculate the number of steps required to approximate a circle
|
|
// (see http://www.angusj.com/clipper2/Docs/Trigonometry.htm)
|
|
// arcTol - when arc_tolerance_ is undefined (0) then curve imprecision
|
|
// will be relative to the size of the offset (delta). Obviously very
|
|
//large offsets will almost always require much less precision.
|
|
double arcTol = (arc_tolerance_ > floating_point_tolerance ?
|
|
std::min(abs_delta, arc_tolerance_) :
|
|
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);
|
|
}
|
|
|
|
//double min_area = PI * Sqr(group_delta_);
|
|
Paths64::const_iterator path_in_it = group.paths_in.cbegin();
|
|
for ( ; path_in_it != group.paths_in.cend(); ++path_in_it)
|
|
{
|
|
Path64::size_type pathLen = path_in_it->size();
|
|
path_out.clear();
|
|
|
|
if (pathLen == 1) // single point
|
|
{
|
|
if (deltaCallback64_)
|
|
{
|
|
group_delta_ = deltaCallback64_(*path_in_it, norms, 0, 0);
|
|
if (group.is_reversed) group_delta_ = -group_delta_;
|
|
abs_delta = std::fabs(group_delta_);
|
|
}
|
|
|
|
if (group_delta_ < 1) continue;
|
|
const Point64& pt = (*path_in_it)[0];
|
|
//single vertex so build a circle or square ...
|
|
if (group.join_type == JoinType::Round)
|
|
{
|
|
double radius = abs_delta;
|
|
size_t steps = steps_per_rad_ > 0 ? static_cast<size_t>(std::ceil(steps_per_rad_ * 2 * PI)) : 0; //#617
|
|
path_out = Ellipse(pt, radius, radius, steps);
|
|
#ifdef USINGZ
|
|
for (auto& p : path_out) p.z = pt.z;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
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 : path_out) p.z = pt.z;
|
|
#endif
|
|
}
|
|
|
|
solution->push_back(path_out);
|
|
continue;
|
|
} // end of offsetting a single point
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
#ifdef USINGZ
|
|
void ClipperOffset::ZCB(const Point64& bot1, const Point64& top1,
|
|
const Point64& bot2, const Point64& top2, Point64& ip)
|
|
{
|
|
if (bot1.z && ((bot1.z == bot2.z) || (bot1.z == top2.z))) ip.z = bot1.z;
|
|
else if (bot2.z && (bot2.z == top1.z)) ip.z = bot2.z;
|
|
else if (top1.z && (top1.z == top2.z)) ip.z = top1.z;
|
|
else if (zCallback64_) zCallback64_(bot1, top1, bot2, top2, ip);
|
|
}
|
|
#endif
|
|
|
|
size_t ClipperOffset::CalcSolutionCapacity()
|
|
{
|
|
size_t result = 0;
|
|
for (const Group& g : groups_)
|
|
result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size();
|
|
return result;
|
|
}
|
|
|
|
bool ClipperOffset::CheckReverseOrientation()
|
|
{
|
|
// nb: this assumes there's consistency in orientation between groups
|
|
bool is_reversed_orientation = false;
|
|
for (const Group& g : groups_)
|
|
if (g.end_type == EndType::Polygon)
|
|
{
|
|
is_reversed_orientation = g.is_reversed;
|
|
break;
|
|
}
|
|
return is_reversed_orientation;
|
|
}
|
|
|
|
void ClipperOffset::ExecuteInternal(double delta)
|
|
{
|
|
error_code_ = 0;
|
|
if (groups_.size() == 0) return;
|
|
solution->reserve(CalcSolutionCapacity());
|
|
|
|
if (std::abs(delta) < 0.5) // ie: offset is insignificant
|
|
{
|
|
Paths64::size_type sol_size = 0;
|
|
for (const Group& group : groups_) sol_size += group.paths_in.size();
|
|
solution->reserve(sol_size);
|
|
for (const Group& group : groups_)
|
|
copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(*solution));
|
|
}
|
|
else
|
|
{
|
|
|
|
temp_lim_ = (miter_limit_ <= 1) ?
|
|
2.0 :
|
|
2.0 / (miter_limit_ * miter_limit_);
|
|
|
|
delta_ = delta;
|
|
std::vector<Group>::iterator git;
|
|
for (git = groups_.begin(); git != groups_.end(); ++git)
|
|
{
|
|
DoGroupOffset(*git);
|
|
if (!error_code_) continue; // all OK
|
|
solution->clear();
|
|
}
|
|
}
|
|
|
|
if (!solution->size()) return;
|
|
|
|
bool paths_reversed = CheckReverseOrientation();
|
|
//clean up self-intersections ...
|
|
Clipper64 c;
|
|
c.PreserveCollinear(false);
|
|
//the solution should retain the orientation of the input
|
|
c.ReverseSolution(reverse_solution_ != paths_reversed);
|
|
#ifdef USINGZ
|
|
auto fp = std::bind(&ClipperOffset::ZCB, this, std::placeholders::_1,
|
|
std::placeholders::_2, std::placeholders::_3,
|
|
std::placeholders::_4, std::placeholders::_5);
|
|
c.SetZCallback(fp);
|
|
#endif
|
|
c.AddSubject(*solution);
|
|
if (solution_tree)
|
|
{
|
|
if (paths_reversed)
|
|
c.Execute(ClipType::Union, FillRule::Negative, *solution_tree);
|
|
else
|
|
c.Execute(ClipType::Union, FillRule::Positive, *solution_tree);
|
|
}
|
|
else
|
|
{
|
|
if (paths_reversed)
|
|
c.Execute(ClipType::Union, FillRule::Negative, *solution);
|
|
else
|
|
c.Execute(ClipType::Union, FillRule::Positive, *solution);
|
|
}
|
|
}
|
|
|
|
void ClipperOffset::Execute(double delta, Paths64& paths)
|
|
{
|
|
paths.clear();
|
|
solution = &paths;
|
|
solution_tree = nullptr;
|
|
ExecuteInternal(delta);
|
|
}
|
|
|
|
|
|
void ClipperOffset::Execute(double delta, PolyTree64& polytree)
|
|
{
|
|
polytree.Clear();
|
|
solution_tree = &polytree;
|
|
solution = new Paths64();
|
|
ExecuteInternal(delta);
|
|
delete solution;
|
|
solution = nullptr;
|
|
}
|
|
|
|
void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths)
|
|
{
|
|
deltaCallback64_ = delta_cb;
|
|
Execute(1.0, paths);
|
|
}
|
|
|
|
} // namespace
|