File indexing completed on 2024-05-12 03:52:08

0001 /*******************************************************************************
0002 * Author    :  Angus Johnson                                                   *
0003 * Date      :  24 September 2023                                               *
0004 * Website   :  http://www.angusj.com                                           *
0005 * Copyright :  Angus Johnson 2010-2023                                         *
0006 * Purpose   :  Path Offset (Inflate/Shrink)                                    *
0007 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
0008 *******************************************************************************/
0009 
0010 #include <cmath>
0011 #include "clipper2/clipper.h"
0012 #include "clipper2/clipper.offset.h"
0013 
0014 namespace Clipper2Lib {
0015 
0016 const double default_arc_tolerance = 0.25;
0017 const double floating_point_tolerance = 1e-12;
0018 
0019 //------------------------------------------------------------------------------
0020 // Miscellaneous methods
0021 //------------------------------------------------------------------------------
0022 
0023 void GetBoundsAndLowestPolyIdx(const Paths64& paths, Rect64& r, int & idx)
0024 {
0025     idx = -1;
0026     r = MaxInvalidRect64;
0027     int64_t lpx = 0;
0028     for (int i = 0; i < static_cast<int>(paths.size()); ++i)
0029         for (const Point64& p : paths[i])
0030         {
0031             if (p.y >= r.bottom)
0032             {
0033                 if (p.y > r.bottom || p.x < lpx)
0034                 {
0035                     idx = i;
0036                     lpx = p.x;
0037                     r.bottom = p.y;
0038                 }
0039             }
0040             else if (p.y < r.top) r.top = p.y;
0041             if (p.x > r.right) r.right = p.x;
0042             else if (p.x < r.left) r.left = p.x;
0043         }
0044     //if (idx < 0) r = Rect64(0, 0, 0, 0);
0045     //if (r.top == INT64_MIN) r.bottom = r.top;
0046     //if (r.left == INT64_MIN) r.left = r.right;
0047 }
0048 
0049 bool IsSafeOffset(const Rect64& r, double abs_delta)
0050 {
0051     return r.left > min_coord + abs_delta &&
0052         r.right < max_coord - abs_delta &&
0053         r.top > min_coord + abs_delta &&
0054         r.bottom < max_coord - abs_delta;
0055 }
0056 
0057 PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
0058 {
0059     double dx, dy, inverse_hypot;
0060     if (pt1 == pt2) return PointD(0.0, 0.0);
0061     dx = static_cast<double>(pt2.x - pt1.x);
0062     dy = static_cast<double>(pt2.y - pt1.y);
0063     inverse_hypot = 1.0 / hypot(dx, dy);
0064     dx *= inverse_hypot;
0065     dy *= inverse_hypot;
0066     return PointD(dy, -dx);
0067 }
0068 
0069 inline bool AlmostZero(double value, double epsilon = 0.001)
0070 {
0071     return std::fabs(value) < epsilon;
0072 }
0073 
0074 inline double Hypot(double x, double y) 
0075 {
0076     //see https://stackoverflow.com/a/32436148/359538
0077     return std::sqrt(x * x + y * y);
0078 }
0079 
0080 inline PointD NormalizeVector(const PointD& vec)
0081 {   
0082     double h = Hypot(vec.x, vec.y);
0083     if (AlmostZero(h)) return PointD(0,0);
0084     double inverseHypot = 1 / h;
0085     return PointD(vec.x * inverseHypot, vec.y * inverseHypot);
0086 }
0087 
0088 inline PointD GetAvgUnitVector(const PointD& vec1, const PointD& vec2)
0089 {
0090     return NormalizeVector(PointD(vec1.x + vec2.x, vec1.y + vec2.y));
0091 }
0092 
0093 inline bool IsClosedPath(EndType et)
0094 {
0095     return et == EndType::Polygon || et == EndType::Joined;
0096 }
0097 
0098 inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta)
0099 {
0100 #ifdef USINGZ
0101     return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
0102 #else
0103     return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta);
0104 #endif
0105 }
0106 
0107 inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta)
0108 {
0109 #ifdef USINGZ
0110     return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z);
0111 #else
0112     return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta);
0113 #endif
0114 }
0115 
0116 inline void NegatePath(PathD& path)
0117 {
0118     for (PointD& pt : path)
0119     {
0120         pt.x = -pt.x;
0121         pt.y = -pt.y;
0122 #ifdef USINGZ
0123         pt.z = pt.z;
0124 #endif
0125     }
0126 }
0127 
0128 //------------------------------------------------------------------------------
0129 // ClipperOffset methods
0130 //------------------------------------------------------------------------------
0131 
0132 void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_)
0133 {
0134     Paths64 paths;
0135     paths.push_back(path);
0136     AddPaths(paths, jt_, et_);
0137 }
0138 
0139 void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_)
0140 {
0141     if (paths.size() == 0) return;
0142     groups_.push_back(Group(paths, jt_, et_));
0143 }
0144 
0145 void ClipperOffset::BuildNormals(const Path64& path)
0146 {
0147     norms.clear();
0148     norms.reserve(path.size());
0149     if (path.size() == 0) return;
0150     Path64::const_iterator path_iter, path_last_iter = --path.cend();
0151     for (path_iter = path.cbegin(); path_iter != path_last_iter; ++path_iter)
0152         norms.push_back(GetUnitNormal(*path_iter,*(path_iter +1)));
0153     norms.push_back(GetUnitNormal(*path_last_iter, *(path.cbegin())));
0154 }
0155 
0156 inline PointD TranslatePoint(const PointD& pt, double dx, double dy)
0157 {
0158 #ifdef USINGZ
0159     return PointD(pt.x + dx, pt.y + dy, pt.z);
0160 #else
0161     return PointD(pt.x + dx, pt.y + dy);
0162 #endif
0163 }
0164 
0165 inline PointD ReflectPoint(const PointD& pt, const PointD& pivot)
0166 {
0167 #ifdef USINGZ
0168     return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y), pt.z);
0169 #else
0170     return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y));
0171 #endif
0172 }
0173 
0174 PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b,
0175     const PointD& pt2a, const PointD& pt2b)
0176 {
0177     if (pt1a.x == pt1b.x) //vertical
0178     {
0179         if (pt2a.x == pt2b.x) return PointD(0, 0);
0180 
0181         double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
0182         double b2 = pt2a.y - m2 * pt2a.x;
0183         return PointD(pt1a.x, m2 * pt1a.x + b2);
0184     }
0185     else if (pt2a.x == pt2b.x) //vertical
0186     {
0187         double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
0188         double b1 = pt1a.y - m1 * pt1a.x;
0189         return PointD(pt2a.x, m1 * pt2a.x + b1);
0190     }
0191     else
0192     {
0193         double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x);
0194         double b1 = pt1a.y - m1 * pt1a.x;
0195         double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x);
0196         double b2 = pt2a.y - m2 * pt2a.x;
0197         if (m1 == m2) return PointD(0, 0);
0198         double x = (b2 - b1) / (m1 - m2);
0199         return PointD(x, m1 * x + b1);
0200     }
0201 }
0202 
0203 void ClipperOffset::DoBevel(Group& group, const Path64& path, size_t j, size_t k)
0204 {
0205     PointD pt1, pt2;
0206     if (j == k)
0207     {
0208         double abs_delta = std::abs(group_delta_);
0209         pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y);
0210         pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y);
0211     } 
0212     else
0213     {
0214         pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y);
0215         pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y);
0216     }
0217     group.path.push_back(Point64(pt1));
0218     group.path.push_back(Point64(pt2));
0219 }
0220 
0221 void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k)
0222 {
0223     PointD vec;
0224     if (j == k) 
0225         vec = PointD(norms[j].y, -norms[j].x);
0226     else
0227         vec = GetAvgUnitVector(
0228             PointD(-norms[k].y, norms[k].x),
0229             PointD(norms[j].y, -norms[j].x));
0230 
0231     double abs_delta = std::abs(group_delta_);
0232 
0233     // now offset the original vertex delta units along unit vector
0234     PointD ptQ = PointD(path[j]);
0235     ptQ = TranslatePoint(ptQ, abs_delta * vec.x, abs_delta * vec.y);
0236     // get perpendicular vertices
0237     PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x);
0238     PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x);
0239     // get 2 vertices along one edge offset
0240     PointD pt3 = GetPerpendicD(path[k], norms[k], group_delta_);
0241     if (j == k)
0242     {
0243         PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
0244         PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
0245 #ifdef USINGZ
0246         pt.z = ptQ.z;
0247 #endif
0248         //get the second intersect point through reflecion
0249         group.path.push_back(Point64(ReflectPoint(pt, ptQ)));
0250         group.path.push_back(Point64(pt));
0251     }
0252     else
0253     {
0254         PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
0255         PointD pt = IntersectPoint(pt1, pt2, pt3, pt4);
0256 #ifdef USINGZ
0257         pt.z = ptQ.z;
0258 #endif
0259         group.path.push_back(Point64(pt));
0260         //get the second intersect point through reflecion
0261         group.path.push_back(Point64(ReflectPoint(pt, ptQ)));
0262     }
0263 }
0264 
0265 void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a)
0266 {
0267     double q = group_delta_ / (cos_a + 1);
0268 #ifdef USINGZ
0269     group.path.push_back(Point64(
0270         path[j].x + (norms[k].x + norms[j].x) * q,
0271         path[j].y + (norms[k].y + norms[j].y) * q,
0272         path[j].z));
0273 #else
0274     group.path.push_back(Point64(
0275         path[j].x + (norms[k].x + norms[j].x) * q,
0276         path[j].y + (norms[k].y + norms[j].y) * q));
0277 #endif
0278 }
0279 
0280 void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle)
0281 {
0282     if (deltaCallback64_) {
0283         // when deltaCallback64_ is assigned, group_delta_ won't be constant, 
0284         // so we'll need to do the following calculations for *every* vertex.
0285         double abs_delta = std::fabs(group_delta_);
0286         double arcTol = (arc_tolerance_ > floating_point_tolerance ?
0287             std::min(abs_delta, arc_tolerance_) :
0288             std::log10(2 + abs_delta) * default_arc_tolerance);
0289         double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
0290         step_sin_ = std::sin(2 * PI / steps_per_360);
0291         step_cos_ = std::cos(2 * PI / steps_per_360);
0292         if (group_delta_ < 0.0) step_sin_ = -step_sin_;
0293         steps_per_rad_ = steps_per_360 / (2 * PI);
0294     }
0295 
0296     Point64 pt = path[j];
0297     PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_);
0298 
0299     if (j == k) offsetVec.Negate();
0300 #ifdef USINGZ
0301     group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
0302 #else
0303     group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
0304 #endif
0305     int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
0306     for (int i = 1; i < steps; ++i) // ie 1 less than steps
0307     {
0308         offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
0309             offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
0310 #ifdef USINGZ
0311         group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
0312 #else
0313         group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
0314 #endif
0315     }
0316     group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_));
0317 }
0318 
0319 void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k)
0320 {
0321     // Let A = change in angle where edges join
0322     // A == 0: ie no change in angle (flat join)
0323     // A == PI: edges 'spike'
0324     // sin(A) < 0: right turning
0325     // cos(A) < 0: change in angle is more than 90 degree
0326 
0327     if (path[j] == path[k]) { k = j; return; }
0328 
0329     double sin_a = CrossProduct(norms[j], norms[k]);
0330     double cos_a = DotProduct(norms[j], norms[k]);
0331     if (sin_a > 1.0) sin_a = 1.0;
0332     else if (sin_a < -1.0) sin_a = -1.0;
0333 
0334     if (deltaCallback64_) {
0335         group_delta_ = deltaCallback64_(path, norms, j, k);
0336         if (group.is_reversed) group_delta_ = -group_delta_;
0337     }
0338     if (std::fabs(group_delta_) <= floating_point_tolerance)
0339     {
0340         group.path.push_back(path[j]);
0341         return;
0342     }
0343 
0344     if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
0345     {
0346         // is concave
0347         group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_));
0348         // this extra point is the only (simple) way to ensure that
0349       // path reversals are fully cleaned with the trailing clipper     
0350         group.path.push_back(path[j]); // (#405)
0351         group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_));
0352     }
0353     else if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424, #526) 
0354     {
0355         DoMiter(group, path, j, k, cos_a);
0356     }
0357     else if (join_type_ == JoinType::Miter)
0358     {
0359         // miter unless the angle is so acute the miter would exceeds ML
0360         if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
0361         else DoSquare(group, path, j, k);
0362     }
0363     else if (cos_a > 0.99 || join_type_ == JoinType::Bevel) 
0364         // ie > 2.5 deg (see above) but less than ~8 deg ( acos(0.99) )
0365         DoBevel(group, path, j, k);
0366     else if (join_type_ == JoinType::Round)
0367         DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
0368     else
0369         DoSquare(group, path, j, k);
0370 }
0371 
0372 void ClipperOffset::OffsetPolygon(Group& group, Path64& path)
0373 {
0374     // when the path is contracting, make sure 
0375     // there is sufficient space to do so.                //#593
0376     // nb: this will have a small impact on performance
0377     double a = Area(path);
0378     // contracting when orientation is opposite offset direction
0379     if ((a < 0) != (group_delta_ < 0)) 
0380     {
0381         Rect64 rec = GetBounds(path);
0382         double offsetMinDim = std::fabs(group_delta_) * 2;
0383         if (offsetMinDim > rec.Width() || offsetMinDim > rec.Height()) return;
0384     }
0385 
0386     for (Path64::size_type j = 0, k = path.size() -1; j < path.size(); k = j, ++j)
0387         OffsetPoint(group, path, j, k);
0388     group.paths_out.push_back(group.path);
0389 }
0390 
0391 void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path)
0392 {
0393     OffsetPolygon(group, path);
0394     std::reverse(path.begin(), path.end());
0395     
0396     //rebuild normals // BuildNormals(path);
0397     std::reverse(norms.begin(), norms.end());
0398     norms.push_back(norms[0]);
0399     norms.erase(norms.begin());
0400     NegatePath(norms);
0401 
0402     group.path.clear();
0403     OffsetPolygon(group, path);
0404 }
0405 
0406 void ClipperOffset::OffsetOpenPath(Group& group, Path64& path)
0407 {
0408     // do the line start cap
0409     if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0);
0410     
0411     if (std::fabs(group_delta_) <= floating_point_tolerance)
0412         group.path.push_back(path[0]);
0413     else
0414     {
0415         switch (end_type_)
0416         {
0417         case EndType::Butt:
0418             DoBevel(group, path, 0, 0);
0419             break;
0420         case EndType::Round:
0421             DoRound(group, path, 0, 0, PI);
0422             break;
0423         default:
0424             DoSquare(group, path, 0, 0);
0425             break;
0426         }
0427     }
0428     
0429     size_t highI = path.size() - 1;
0430     // offset the left side going forward
0431     for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j)
0432         OffsetPoint(group, path, j, k);
0433 
0434     // reverse normals 
0435     for (size_t i = highI; i > 0; --i)
0436         norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y);
0437     norms[0] = norms[highI];
0438 
0439     // do the line end cap
0440     if (deltaCallback64_)
0441         group_delta_ = deltaCallback64_(path, norms, highI, highI);
0442 
0443     if (std::fabs(group_delta_) <= floating_point_tolerance)
0444         group.path.push_back(path[highI]);
0445     else
0446     {
0447         switch (end_type_)
0448         {
0449         case EndType::Butt:
0450             DoBevel(group, path, highI, highI);
0451             break;
0452         case EndType::Round:
0453             DoRound(group, path, highI, highI, PI);
0454             break;
0455         default:
0456             DoSquare(group, path, highI, highI);
0457             break;
0458         }
0459     }
0460 
0461     for (size_t j = highI, k = 0; j > 0; k = j, --j)
0462         OffsetPoint(group, path, j, k);
0463     group.paths_out.push_back(group.path);
0464 }
0465 
0466 void ClipperOffset::DoGroupOffset(Group& group)
0467 {
0468     Rect64 r;
0469     int idx = -1;
0470     //the lowermost polygon must be an outer polygon. So we can use that as the
0471     //designated orientation for outer polygons (needed for tidy-up clipping)
0472     GetBoundsAndLowestPolyIdx(group.paths_in, r, idx);
0473     if (idx < 0) return;
0474 
0475     if (group.end_type == EndType::Polygon)
0476     {
0477         double area = Area(group.paths_in[idx]);
0478         //if (area == 0) return; // probably unhelpful (#430)
0479         group.is_reversed = (area < 0);
0480         if (group.is_reversed) group_delta_ = -delta_;
0481         else group_delta_ = delta_;
0482     } 
0483     else
0484     {
0485         group.is_reversed = false;
0486         group_delta_ = std::abs(delta_) * 0.5;
0487     }
0488 
0489     double abs_delta = std::fabs(group_delta_);
0490     // do range checking
0491     if (!IsSafeOffset(r, abs_delta))
0492     {
0493         DoError(range_error_i);
0494         error_code_ |= range_error_i;
0495         return;
0496     }
0497 
0498     join_type_  = group.join_type;
0499     end_type_ = group.end_type;
0500 
0501     if (!deltaCallback64_ && 
0502         (group.join_type == JoinType::Round || group.end_type == EndType::Round))
0503     {
0504         //calculate a sensible number of steps (for 360 deg for the given offset)
0505         // arcTol - when arc_tolerance_ is undefined (0), the amount of 
0506         // curve imprecision that's allowed is based on the size of the 
0507         // offset (delta). Obviously very large offsets will almost always 
0508         // require much less precision. See also offset_triginometry2.svg
0509         double arcTol = (arc_tolerance_ > floating_point_tolerance ?
0510             std::min(abs_delta, arc_tolerance_) :
0511             std::log10(2 + abs_delta) * default_arc_tolerance);
0512 
0513         double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI);
0514         step_sin_ = std::sin(2 * PI / steps_per_360);
0515         step_cos_ = std::cos(2 * PI / steps_per_360);
0516         if (group_delta_ < 0.0) step_sin_ = -step_sin_;
0517         steps_per_rad_ = steps_per_360 / (2 * PI);
0518     }
0519 
0520     bool is_joined =
0521         (end_type_ == EndType::Polygon) ||
0522         (end_type_ == EndType::Joined);
0523     Paths64::iterator path_iter;
0524     for(path_iter = group.paths_in.begin(); path_iter != group.paths_in.end(); ++path_iter)
0525     {
0526         Path64 &path = *path_iter;
0527         StripDuplicates(path, is_joined);
0528         Path64::size_type cnt = path.size();
0529         if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon)) 
0530             continue;
0531 
0532         group.path.clear();
0533         if (cnt == 1) // single point - only valid with open paths
0534         {
0535             if (group_delta_ < 1) continue;
0536             //single vertex so build a circle or square ...
0537             if (group.join_type == JoinType::Round)
0538             {
0539                 double radius = abs_delta;
0540                 int steps = static_cast<int>(std::ceil(steps_per_rad_ * 2 * PI)); //#617
0541                 group.path = Ellipse(path[0], radius, radius, steps);
0542 #ifdef USINGZ
0543                 for (auto& p : group.path) p.z = path[0].z;
0544 #endif
0545             }
0546             else
0547             {
0548                 int d = (int)std::ceil(abs_delta);
0549                 r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d);
0550                 group.path = r.AsPath();
0551 #ifdef USINGZ
0552                 for (auto& p : group.path) p.z = path[0].z;
0553 #endif
0554             }
0555             group.paths_out.push_back(group.path);
0556         }
0557         else
0558         {
0559             if ((cnt == 2) && (group.end_type == EndType::Joined))
0560             {
0561                 if (group.join_type == JoinType::Round)
0562                     end_type_ = EndType::Round;
0563                 else
0564                     end_type_ = EndType::Square;
0565             }
0566 
0567             BuildNormals(path);
0568             if (end_type_ == EndType::Polygon) OffsetPolygon(group, path);
0569             else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path);
0570             else OffsetOpenPath(group, path);
0571         }
0572     }
0573     solution.reserve(solution.size() + group.paths_out.size());
0574     copy(group.paths_out.begin(), group.paths_out.end(), back_inserter(solution));
0575     group.paths_out.clear();
0576 }
0577 
0578 void ClipperOffset::ExecuteInternal(double delta)
0579 {
0580     error_code_ = 0;
0581     solution.clear();
0582     if (groups_.size() == 0) return;
0583 
0584     if (std::abs(delta) < 0.5)
0585     {
0586         for (const Group& group : groups_)
0587         {
0588             solution.reserve(solution.size() + group.paths_in.size());
0589             copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(solution));
0590         }
0591     } 
0592     else
0593     {
0594         temp_lim_ = (miter_limit_ <= 1) ?
0595             2.0 :
0596             2.0 / (miter_limit_ * miter_limit_);
0597 
0598         delta_ = delta;
0599         std::vector<Group>::iterator git;
0600         for (git = groups_.begin(); git != groups_.end(); ++git)
0601         {
0602             DoGroupOffset(*git);
0603             if (!error_code_) continue; // all OK
0604             solution.clear();
0605         }
0606     }
0607 }
0608 
0609 void ClipperOffset::Execute(double delta, Paths64& paths)
0610 {
0611     paths.clear();
0612 
0613     ExecuteInternal(delta);
0614     if (!solution.size()) return;
0615 
0616     paths = solution;
0617     //clean up self-intersections ...
0618     Clipper64 c;
0619     c.PreserveCollinear = false;
0620     //the solution should retain the orientation of the input
0621     c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed;
0622 #ifdef USINGZ
0623     if (zCallback64_) {
0624         c.SetZCallback(zCallback64_);
0625     }
0626 #endif
0627     c.AddSubject(solution);
0628     if (groups_[0].is_reversed)
0629         c.Execute(ClipType::Union, FillRule::Negative, paths);
0630     else
0631         c.Execute(ClipType::Union, FillRule::Positive, paths);
0632 }
0633 
0634 
0635 void ClipperOffset::Execute(double delta, PolyTree64& polytree)
0636 {
0637     polytree.Clear();
0638 
0639     ExecuteInternal(delta);
0640     if (!solution.size()) return;
0641 
0642     //clean up self-intersections ...
0643     Clipper64 c;
0644     c.PreserveCollinear = false;
0645     //the solution should retain the orientation of the input
0646     c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed;
0647 #ifdef USINGZ
0648     if (zCallback64_) {
0649         c.SetZCallback(zCallback64_);
0650     }
0651 #endif
0652     c.AddSubject(solution);
0653     if (groups_[0].is_reversed)
0654         c.Execute(ClipType::Union, FillRule::Negative, polytree);
0655     else
0656         c.Execute(ClipType::Union, FillRule::Positive, polytree);
0657 }
0658 
0659 void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths)
0660 {
0661     deltaCallback64_ = delta_cb;
0662     Execute(1.0, paths);
0663 }
0664 
0665 } // namespace