File indexing completed on 2025-04-20 03:36:30
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