1 // 2 // 3 // Author : Angus Johnson * 4 // Version : 4.8.4 * 5 // Date : 1 June 2012 * 6 // Website : http://www.angusj.com * 7 // Copyright : Angus Johnson 2010-2012 * 8 // * 9 // License: * 10 // Use, modification & distribution is subject to Boost Software License Ver 1. * 11 // http://www.boost.org/LICENSE_1_0.txt * 12 // * 13 // Attributions: * 14 // The code in this library is an extension of Bala Vatti's clipping algorithm: * 15 // "A generic solution to polygon clipping" * 16 // Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * 17 // http://portal.acm.org/citation.cfm?id=129906 * 18 // * 19 // Computer graphics and geometric modeling: implementation and algorithms * 20 // By Max K. Agoston * 21 // Springer; 1 edition (January 4, 2005) * 22 // http://books.google.com/books?q=vatti+clipping+agoston * 23 // * 24 // See also: * 25 // "Polygon Offsetting by Computing Winding Numbers" * 26 // Paper no. DETC2005-85513 pp. 565-575 * 27 // ASME 2005 International Design Engineering Technical Conferences * 28 // and Computers and Information in Engineering Conference (IDETC/CIE2005) * 29 // September 24–28, 2005 , Long Beach, California, USA * 30 // http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * 31 // * 32 // 33 34 // 35 // This is a translation of the Delphi Clipper library and the naming style 36 // used has retained a Delphi flavour. 37 // 38 39 // 40 // This Clipper Library code was imported into Pinta on June 6th, 2012 by 41 // Andrew Davis of GSoC 2012 for the MagicWandTool selection modes 42 // implementation. It calculates "boolean clipping operations" (union, 43 // difference (exclude), xor, and intersect) on any type, number, and/or 44 // combination of polygons. It is independent C# code that strictly serves 45 // this purpose. More information can be found here: 46 // 47 // http://www.angusj.com/delphi/clipper.php 48 // http://sourceforge.net/projects/polyclipping/ 49 // 50 // - Andrew Davis 51 // 52 53 using System; 54 using System.Collections.Generic; 55 56 namespace ClipperLibrary 57 { 58 59 using Polygon = List<IntPoint>; 60 using Polygons = List<List<IntPoint>>; 61 using ExPolygons = List<ExPolygon>; 62 63 //------------------------------------------------------------------------------ 64 // Int128 struct (enables safe math on signed 64bit integers) 65 // eg Int128 val1((Int64)9223372036854775807); //ie 2^63 -1 66 // Int128 val2((Int64)9223372036854775807); 67 // Int128 val3 = val1 * val2; 68 // val3.ToString => "85070591730234615847396907784232501249" (8.5e+37) 69 //------------------------------------------------------------------------------ 70 71 internal struct Int128 72 { 73 private Int64 hi; 74 private Int64 lo; 75 Int128ClipperLibrary.Int12876 public Int128(Int64 lo) 77 { 78 this.lo = lo; 79 if (lo < 0) 80 this.hi = -1; 81 else 82 this.hi = 0; 83 } 84 Int128ClipperLibrary.Int12885 public Int128(Int64 lo, Int64 hi) 86 { 87 this.lo = lo; 88 this.hi = hi; 89 } 90 Int128ClipperLibrary.Int12891 public Int128(Int128 val) 92 { 93 hi = val.hi; 94 lo = val.lo; 95 } 96 IsNegativeClipperLibrary.Int12897 public bool IsNegative() 98 { 99 return hi < 0; 100 } 101 operator ==ClipperLibrary.Int128102 public static bool operator ==(Int128 val1, Int128 val2) 103 { 104 if ((object)val1 == (object)val2) return true; 105 else if ((object)val1 == null || (object)val2 == null) return false; 106 return (val1.hi == val2.hi && val1.lo == val2.lo); 107 } 108 operator !=ClipperLibrary.Int128109 public static bool operator!= (Int128 val1, Int128 val2) 110 { 111 return !(val1 == val2); 112 } 113 EqualsClipperLibrary.Int128114 public override bool Equals(System.Object obj) 115 { 116 if (obj == null || !(obj is Int128)) 117 return false; 118 Int128 i128 = (Int128)obj; 119 return (i128.hi == hi && i128.lo == lo); 120 } 121 GetHashCodeClipperLibrary.Int128122 public override int GetHashCode() 123 { 124 return hi.GetHashCode() ^ lo.GetHashCode(); 125 } 126 operator >ClipperLibrary.Int128127 public static bool operator> (Int128 val1, Int128 val2) 128 { 129 if (val1.hi != val2.hi) 130 return val1.hi > val2.hi; 131 else 132 return val1.lo > val2.lo; 133 } 134 operator <ClipperLibrary.Int128135 public static bool operator< (Int128 val1, Int128 val2) 136 { 137 if (val1.hi != val2.hi) 138 return val1.hi < val2.hi; 139 else 140 return val1.lo < val2.lo; 141 } 142 operator +ClipperLibrary.Int128143 public static Int128 operator+ (Int128 lhs, Int128 rhs) 144 { 145 lhs.hi += rhs.hi; 146 lhs.lo += rhs.lo; 147 if ( (UInt64)lhs.lo < (UInt64)rhs.lo) lhs.hi++; 148 return lhs; 149 } 150 operator -ClipperLibrary.Int128151 public static Int128 operator- (Int128 lhs, Int128 rhs) 152 { 153 return lhs + -rhs; 154 } 155 operator -ClipperLibrary.Int128156 public static Int128 operator -(Int128 val) 157 { 158 if (val.lo == 0) { 159 if (val.hi == 0) return val; 160 return new Int128(0, -val.hi); 161 } 162 else return new Int128(-val.lo, ~val.hi); 163 } 164 165 //nb: Constructing two new Int128 objects every time we want to multiply longs 166 //is slow. So, although calling the Int128Mul method doesn't look as clean, the 167 //code runs significantly faster than if we'd used the * operator. 168 Int128MulClipperLibrary.Int128169 public static Int128 Int128Mul(Int64 lhs, Int64 rhs) 170 { 171 bool negate = (lhs < 0) != (rhs < 0); 172 if (lhs < 0) lhs = -lhs; 173 if (rhs < 0) rhs = -rhs; 174 UInt64 int1Hi = (UInt64)lhs >> 32; 175 UInt64 int1Lo = (UInt64)lhs & 0xFFFFFFFF; 176 UInt64 int2Hi = (UInt64)rhs >> 32; 177 UInt64 int2Lo = (UInt64)rhs & 0xFFFFFFFF; 178 179 //nb: see comments in clipper.pas 180 UInt64 a = int1Hi * int2Hi; 181 UInt64 b = int1Lo * int2Lo; 182 UInt64 c = int1Hi * int2Lo + int1Lo * int2Hi; 183 184 Int64 lo, hi; 185 hi = (Int64)(a + (c >> 32)); 186 187 lo = (Int64)(c << 32); 188 lo += (Int64)b; 189 if ((UInt64)lo < b) hi++; 190 var result = new Int128(lo, hi); 191 return negate ? -result : result; 192 } 193 operator /ClipperLibrary.Int128194 public static Int128 operator /(Int128 lhs, Int128 rhs) 195 { 196 if (rhs.lo == 0 && rhs.hi == 0) 197 throw new ClipperException("Int128: divide by zero"); 198 bool negate = (rhs.hi < 0) != (lhs.hi < 0); 199 Int128 result = new Int128(lhs), denom = new Int128(rhs); 200 if (result.hi < 0) result = -result; 201 if (denom.hi < 0) denom = -denom; 202 if (denom > result) return new Int128(0); //result is only a fraction of 1 203 denom = -denom; 204 205 Int128 p = new Int128(0); 206 for (int i = 0; i < 128; ++i) 207 { 208 p.hi = p.hi << 1; 209 if (p.lo < 0) p.hi++; 210 p.lo = (Int64)p.lo << 1; 211 if (result.hi < 0) p.lo++; 212 result.hi = result.hi << 1; 213 if (result.lo < 0) result.hi++; 214 result.lo = (Int64)result.lo << 1; 215 if (p.hi >= 0) 216 { 217 p += denom; 218 result.lo++; 219 } 220 } 221 return negate ? -result : result; 222 } 223 ToDoubleClipperLibrary.Int128224 public double ToDouble() 225 { 226 const double shift64 = 18446744073709551616.0; //2^64 227 const double bit64 = 9223372036854775808.0; 228 if (hi < 0) 229 { 230 Int128 tmp = new Int128(this); 231 tmp = -tmp; 232 if (tmp.lo < 0) 233 return (double)tmp.lo - bit64 - tmp.hi * shift64; 234 else 235 return -(double)tmp.lo - tmp.hi * shift64; 236 } 237 else if (lo < 0) 238 return -(double)lo + bit64 + hi * shift64; 239 else 240 return (double)lo + (double)hi * shift64; 241 } 242 243 ////for bug testing ... 244 //public override string ToString() 245 //{ 246 // int r = 0; 247 // Int128 tmp = new Int128(0), val = new Int128(this); 248 // if (hi < 0) Negate(val); 249 // StringBuilder builder = new StringBuilder(50); 250 // while (val.hi != 0 || val.lo != 0) 251 // { 252 // Div10(val, ref tmp, ref r); 253 // builder.Insert(0, (char)('0' + r)); 254 // val = tmp; 255 // } 256 // if (hi < 0) return '-' + builder.ToString(); 257 // if (builder.Length == 0) return "0"; 258 // return builder.ToString(); 259 //} 260 261 ////debugging only ... 262 //private void Div10(Int128 val, ref Int128 result, ref int remainder) 263 //{ 264 // remainder = 0; 265 // result = new Int128(0); 266 // for (int i = 63; i >= 0; --i) 267 // { 268 // if ((val.hi & ((Int64)1 << i)) != 0) 269 // remainder = (remainder * 2) + 1; 270 // else 271 // remainder *= 2; 272 // if (remainder >= 10) 273 // { 274 // result.hi += ((Int64)1 << i); 275 // remainder -= 10; 276 // } 277 // } 278 // for (int i = 63; i >= 0; --i) 279 // { 280 // if ((val.lo & ((Int64)1 << i)) != 0) 281 // remainder = (remainder * 2) + 1; 282 // else 283 // remainder *= 2; 284 // if (remainder >= 10) 285 // { 286 // result.lo += ((Int64)1 << i); 287 // remainder -= 10; 288 // } 289 // } 290 //} 291 }; 292 293 //------------------------------------------------------------------------------ 294 //------------------------------------------------------------------------------ 295 296 public struct IntPoint 297 { 298 public Int64 X; 299 public Int64 Y; 300 IntPointClipperLibrary.IntPoint301 public IntPoint(Int64 X, Int64 Y) 302 { 303 this.X = X; this.Y = Y; 304 } 305 IntPointClipperLibrary.IntPoint306 public IntPoint(IntPoint pt) 307 { 308 this.X = pt.X; this.Y = pt.Y; 309 } 310 } 311 312 public struct IntRect 313 { 314 public Int64 left; 315 public Int64 top; 316 public Int64 right; 317 public Int64 bottom; 318 IntRectClipperLibrary.IntRect319 public IntRect(Int64 l, Int64 t, Int64 r, Int64 b) 320 { 321 this.left = l; this.top = t; 322 this.right = r; this.bottom = b; 323 } 324 } 325 326 public struct ExPolygon 327 { 328 public Polygon outer; 329 public Polygons holes; 330 } 331 332 public enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; 333 public enum PolyType { ptSubject, ptClip }; 334 //By far the most widely used winding rules for polygon filling are 335 //EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) 336 //Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) 337 //see http://glprogramming.com/red/chapter11.html 338 public enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; 339 public enum JoinType { jtSquare, jtRound, jtMiter }; 340 341 342 [Flags] 343 internal enum EdgeSide { esNeither = 0, esLeft = 1, esRight = 2, esBoth = 3 }; 344 [Flags] 345 internal enum Protects { ipNone = 0, ipLeft = 1, ipRight = 2, ipBoth = 3 }; 346 internal enum Direction { dRightToLeft, dLeftToRight }; 347 348 internal class TEdge { 349 public Int64 xbot; 350 public Int64 ybot; 351 public Int64 xcurr; 352 public Int64 ycurr; 353 public Int64 xtop; 354 public Int64 ytop; 355 public double dx; 356 public Int64 tmpX; 357 public PolyType polyType; 358 public EdgeSide side; 359 public int windDelta; //1 or -1 depending on winding direction 360 public int windCnt; 361 public int windCnt2; //winding count of the opposite polytype 362 public int outIdx; 363 public TEdge next; 364 public TEdge prev; 365 public TEdge nextInLML; 366 public TEdge nextInAEL; 367 public TEdge prevInAEL; 368 public TEdge nextInSEL; 369 public TEdge prevInSEL; 370 }; 371 372 internal class IntersectNode 373 { 374 public TEdge edge1; 375 public TEdge edge2; 376 public IntPoint pt; 377 public IntersectNode next; 378 }; 379 380 internal class LocalMinima 381 { 382 public Int64 Y; 383 public TEdge leftBound; 384 public TEdge rightBound; 385 public LocalMinima next; 386 }; 387 388 internal class Scanbeam 389 { 390 public Int64 Y; 391 public Scanbeam next; 392 }; 393 394 internal class OutRec 395 { 396 public int idx; 397 public bool isHole; 398 public OutRec FirstLeft; 399 public OutRec AppendLink; 400 public OutPt pts; 401 public OutPt bottomPt; 402 public OutPt bottomFlag; 403 public EdgeSide sides; 404 }; 405 406 internal class OutPt 407 { 408 public int idx; 409 public IntPoint pt; 410 public OutPt next; 411 public OutPt prev; 412 }; 413 414 internal class JoinRec 415 { 416 public IntPoint pt1a; 417 public IntPoint pt1b; 418 public int poly1Idx; 419 public IntPoint pt2a; 420 public IntPoint pt2b; 421 public int poly2Idx; 422 }; 423 424 internal class HorzJoinRec 425 { 426 public TEdge edge; 427 public int savedIdx; 428 }; 429 430 public class ClipperBase 431 { 432 protected const double horizontal = -3.4E+38; 433 internal const Int64 loRange = 1518500249; //sqrt(2^63 -1)/2 434 internal const Int64 hiRange = 6521908912666391106L; //sqrt(2^127 -1)/2 435 436 internal LocalMinima m_MinimaList; 437 internal LocalMinima m_CurrentLM; 438 internal List<List<TEdge>> m_edges = new List<List<TEdge>>(); 439 internal bool m_UseFullRange; 440 441 //------------------------------------------------------------------------------ 442 PointsEqual(IntPoint pt1, IntPoint pt2)443 protected static bool PointsEqual(IntPoint pt1, IntPoint pt2) 444 { 445 return ( pt1.X == pt2.X && pt1.Y == pt2.Y ); 446 } 447 //------------------------------------------------------------------------------ 448 PointIsVertex(IntPoint pt, OutPt pp)449 internal bool PointIsVertex(IntPoint pt, OutPt pp) 450 { 451 OutPt pp2 = pp; 452 do 453 { 454 if (PointsEqual(pp2.pt, pt)) return true; 455 pp2 = pp2.next; 456 } 457 while (pp2 != pp); 458 return false; 459 } 460 //------------------------------------------------------------------------------ 461 PointInPolygon(IntPoint pt, OutPt pp, bool UseFulllongRange)462 internal bool PointInPolygon(IntPoint pt, OutPt pp, bool UseFulllongRange) 463 { 464 OutPt pp2 = pp; 465 bool result = false; 466 if (UseFulllongRange) 467 { 468 do 469 { 470 if ((((pp2.pt.Y <= pt.Y) && (pt.Y < pp2.prev.pt.Y)) || 471 ((pp2.prev.pt.Y <= pt.Y) && (pt.Y < pp2.pt.Y))) && 472 new Int128(pt.X - pp2.pt.X) < 473 Int128.Int128Mul(pp2.prev.pt.X - pp2.pt.X, pt.Y - pp2.pt.Y) / 474 new Int128(pp2.prev.pt.Y - pp2.pt.Y)) 475 result = !result; 476 pp2 = pp2.next; 477 } 478 while (pp2 != pp); 479 } 480 else 481 { 482 do 483 { 484 if ((((pp2.pt.Y <= pt.Y) && (pt.Y < pp2.prev.pt.Y)) || 485 ((pp2.prev.pt.Y <= pt.Y) && (pt.Y < pp2.pt.Y))) && 486 (pt.X - pp2.pt.X < (pp2.prev.pt.X - pp2.pt.X) * (pt.Y - pp2.pt.Y) / 487 (pp2.prev.pt.Y - pp2.pt.Y))) result = !result; 488 pp2 = pp2.next; 489 } 490 while (pp2 != pp); 491 } 492 return result; 493 } 494 //------------------------------------------------------------------------------ 495 SlopesEqual(TEdge e1, TEdge e2, bool UseFullRange)496 internal bool SlopesEqual(TEdge e1, TEdge e2, bool UseFullRange) 497 { 498 if (UseFullRange) 499 return Int128.Int128Mul(e1.ytop - e1.ybot, e2.xtop - e2.xbot) == 500 Int128.Int128Mul(e1.xtop - e1.xbot, e2.ytop - e2.ybot); 501 else return (Int64)(e1.ytop - e1.ybot) * (e2.xtop - e2.xbot) - 502 (Int64)(e1.xtop - e1.xbot)*(e2.ytop - e2.ybot) == 0; 503 } 504 //------------------------------------------------------------------------------ 505 SlopesEqual(IntPoint pt1, IntPoint pt2, IntPoint pt3, bool UseFullRange)506 protected bool SlopesEqual(IntPoint pt1, IntPoint pt2, 507 IntPoint pt3, bool UseFullRange) 508 { 509 if (UseFullRange) 510 return Int128.Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) == 511 Int128.Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y); 512 else return 513 (Int64)(pt1.Y - pt2.Y) * (pt2.X - pt3.X) - (Int64)(pt1.X - pt2.X) * (pt2.Y - pt3.Y) == 0; 514 } 515 //------------------------------------------------------------------------------ 516 SlopesEqual(IntPoint pt1, IntPoint pt2, IntPoint pt3, IntPoint pt4, bool UseFullRange)517 protected bool SlopesEqual(IntPoint pt1, IntPoint pt2, 518 IntPoint pt3, IntPoint pt4, bool UseFullRange) 519 { 520 if (UseFullRange) 521 return Int128.Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) == 522 Int128.Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y); 523 else return 524 (Int64)(pt1.Y - pt2.Y) * (pt3.X - pt4.X) - (Int64)(pt1.X - pt2.X) * (pt3.Y - pt4.Y) == 0; 525 } 526 //------------------------------------------------------------------------------ 527 ClipperBase()528 internal ClipperBase() //constructor (nb: no external instantiation) 529 { 530 m_MinimaList = null; 531 m_CurrentLM = null; 532 m_UseFullRange = false; 533 } 534 //------------------------------------------------------------------------------ 535 536 //destructor - commented out since I gather this impedes the GC 537 //~ClipperBase() 538 //{ 539 // Clear(); 540 //} 541 //------------------------------------------------------------------------------ 542 Clear()543 public virtual void Clear() 544 { 545 DisposeLocalMinimaList(); 546 for (int i = 0; i < m_edges.Count; ++i) 547 { 548 for (int j = 0; j < m_edges[i].Count; ++j) m_edges[i][j] = null; 549 m_edges[i].Clear(); 550 } 551 m_edges.Clear(); 552 m_UseFullRange = false; 553 } 554 //------------------------------------------------------------------------------ 555 DisposeLocalMinimaList()556 private void DisposeLocalMinimaList() 557 { 558 while( m_MinimaList != null ) 559 { 560 LocalMinima tmpLm = m_MinimaList.next; 561 m_MinimaList = null; 562 m_MinimaList = tmpLm; 563 } 564 m_CurrentLM = null; 565 } 566 //------------------------------------------------------------------------------ 567 AddPolygons(Polygons ppg, PolyType polyType)568 public bool AddPolygons(Polygons ppg, PolyType polyType) 569 { 570 bool result = false; 571 for (int i = 0; i < ppg.Count; ++i) 572 if (AddPolygon(ppg[i], polyType)) result = true; 573 return result; 574 } 575 //------------------------------------------------------------------------------ 576 AddPolygon(Polygon pg, PolyType polyType)577 public bool AddPolygon(Polygon pg, PolyType polyType) 578 { 579 int len = pg.Count; 580 if (len < 3) return false; 581 Polygon p = new Polygon(len); 582 p.Add(new IntPoint(pg[0].X, pg[0].Y)); 583 int j = 0; 584 for (int i = 1; i < len; ++i) 585 { 586 587 Int64 maxVal; 588 if (m_UseFullRange) maxVal = hiRange; else maxVal = loRange; 589 if (Math.Abs(pg[i].X) > maxVal || Math.Abs(pg[i].Y) > maxVal) 590 { 591 if (Math.Abs(pg[i].X) > hiRange || Math.Abs(pg[i].Y) > hiRange) 592 throw new ClipperException("Coordinate exceeds range bounds"); 593 maxVal = hiRange; 594 m_UseFullRange = true; 595 } 596 597 if (PointsEqual(p[j], pg[i])) continue; 598 else if (j > 0 && SlopesEqual(p[j-1], p[j], pg[i], m_UseFullRange)) 599 { 600 if (PointsEqual(p[j-1], pg[i])) j--; 601 } else j++; 602 if (j < p.Count) 603 p[j] = pg[i]; else 604 p.Add(new IntPoint(pg[i].X, pg[i].Y)); 605 } 606 if (j < 2) return false; 607 608 len = j+1; 609 while (len > 2) 610 { 611 //nb: test for point equality before testing slopes ... 612 if (PointsEqual(p[j], p[0])) j--; 613 else if (PointsEqual(p[0], p[1]) || SlopesEqual(p[j], p[0], p[1], m_UseFullRange)) 614 p[0] = p[j--]; 615 else if (SlopesEqual(p[j - 1], p[j], p[0], m_UseFullRange)) j--; 616 else if (SlopesEqual(p[0], p[1], p[2], m_UseFullRange)) 617 { 618 for (int i = 2; i <= j; ++i) p[i - 1] = p[i]; 619 j--; 620 } 621 else break; 622 len--; 623 } 624 if (len < 3) return false; 625 626 //create a new edge array ... 627 List<TEdge> edges = new List<TEdge>(len); 628 for (int i = 0; i < len; i++) edges.Add(new TEdge()); 629 m_edges.Add(edges); 630 631 //convert vertices to a double-linked-list of edges and initialize ... 632 edges[0].xcurr = p[0].X; 633 edges[0].ycurr = p[0].Y; 634 InitEdge(edges[len-1], edges[0], edges[len-2], p[len-1], polyType); 635 for (int i = len-2; i > 0; --i) 636 InitEdge(edges[i], edges[i+1], edges[i-1], p[i], polyType); 637 InitEdge(edges[0], edges[1], edges[len-1], p[0], polyType); 638 639 //reset xcurr & ycurr and find 'eHighest' (given the Y axis coordinates 640 //increase downward so the 'highest' edge will have the smallest ytop) ... 641 TEdge e = edges[0]; 642 TEdge eHighest = e; 643 do 644 { 645 e.xcurr = e.xbot; 646 e.ycurr = e.ybot; 647 if (e.ytop < eHighest.ytop) eHighest = e; 648 e = e.next; 649 } 650 while ( e != edges[0]); 651 652 //make sure eHighest is positioned so the following loop works safely ... 653 if (eHighest.windDelta > 0) eHighest = eHighest.next; 654 if (eHighest.dx == horizontal) eHighest = eHighest.next; 655 656 //finally insert each local minima ... 657 e = eHighest; 658 do { 659 e = AddBoundsToLML(e); 660 } 661 while( e != eHighest ); 662 return true; 663 } 664 //------------------------------------------------------------------------------ 665 InitEdge(TEdge e, TEdge eNext, TEdge ePrev, IntPoint pt, PolyType polyType)666 private void InitEdge(TEdge e, TEdge eNext, 667 TEdge ePrev, IntPoint pt, PolyType polyType) 668 { 669 e.next = eNext; 670 e.prev = ePrev; 671 e.xcurr = pt.X; 672 e.ycurr = pt.Y; 673 if (e.ycurr >= e.next.ycurr) 674 { 675 e.xbot = e.xcurr; 676 e.ybot = e.ycurr; 677 e.xtop = e.next.xcurr; 678 e.ytop = e.next.ycurr; 679 e.windDelta = 1; 680 } else 681 { 682 e.xtop = e.xcurr; 683 e.ytop = e.ycurr; 684 e.xbot = e.next.xcurr; 685 e.ybot = e.next.ycurr; 686 e.windDelta = -1; 687 } 688 SetDx(e); 689 e.polyType = polyType; 690 e.outIdx = -1; 691 } 692 //------------------------------------------------------------------------------ 693 SetDx(TEdge e)694 private void SetDx(TEdge e) 695 { 696 if (e.ybot == e.ytop) e.dx = horizontal; 697 else e.dx = (double)(e.xtop - e.xbot)/(e.ytop - e.ybot); 698 } 699 //--------------------------------------------------------------------------- 700 AddBoundsToLML(TEdge e)701 TEdge AddBoundsToLML(TEdge e) 702 { 703 //Starting at the top of one bound we progress to the bottom where there's 704 //a local minima. We then go to the top of the next bound. These two bounds 705 //form the left and right (or right and left) bounds of the local minima. 706 e.nextInLML = null; 707 e = e.next; 708 for (;;) 709 { 710 if ( e.dx == horizontal ) 711 { 712 //nb: proceed through horizontals when approaching from their right, 713 // but break on horizontal minima if approaching from their left. 714 // This ensures 'local minima' are always on the left of horizontals. 715 if (e.next.ytop < e.ytop && e.next.xbot > e.prev.xbot) break; 716 if (e.xtop != e.prev.xbot) SwapX(e); 717 e.nextInLML = e.prev; 718 } 719 else if (e.ycurr == e.prev.ycurr) break; 720 else e.nextInLML = e.prev; 721 e = e.next; 722 } 723 724 //e and e.prev are now at a local minima ... 725 LocalMinima newLm = new LocalMinima(); 726 newLm.next = null; 727 newLm.Y = e.prev.ybot; 728 729 if ( e.dx == horizontal ) //horizontal edges never start a left bound 730 { 731 if (e.xbot != e.prev.xbot) SwapX(e); 732 newLm.leftBound = e.prev; 733 newLm.rightBound = e; 734 } else if (e.dx < e.prev.dx) 735 { 736 newLm.leftBound = e.prev; 737 newLm.rightBound = e; 738 } else 739 { 740 newLm.leftBound = e; 741 newLm.rightBound = e.prev; 742 } 743 newLm.leftBound.side = EdgeSide.esLeft; 744 newLm.rightBound.side = EdgeSide.esRight; 745 InsertLocalMinima( newLm ); 746 747 for (;;) 748 { 749 if ( e.next.ytop == e.ytop && e.next.dx != horizontal ) break; 750 e.nextInLML = e.next; 751 e = e.next; 752 if ( e.dx == horizontal && e.xbot != e.prev.xtop) SwapX(e); 753 } 754 return e.next; 755 } 756 //------------------------------------------------------------------------------ 757 InsertLocalMinima(LocalMinima newLm)758 private void InsertLocalMinima(LocalMinima newLm) 759 { 760 if( m_MinimaList == null ) 761 { 762 m_MinimaList = newLm; 763 } 764 else if( newLm.Y >= m_MinimaList.Y ) 765 { 766 newLm.next = m_MinimaList; 767 m_MinimaList = newLm; 768 } else 769 { 770 LocalMinima tmpLm = m_MinimaList; 771 while( tmpLm.next != null && ( newLm.Y < tmpLm.next.Y ) ) 772 tmpLm = tmpLm.next; 773 newLm.next = tmpLm.next; 774 tmpLm.next = newLm; 775 } 776 } 777 //------------------------------------------------------------------------------ 778 PopLocalMinima()779 protected void PopLocalMinima() 780 { 781 if (m_CurrentLM == null) return; 782 m_CurrentLM = m_CurrentLM.next; 783 } 784 //------------------------------------------------------------------------------ 785 SwapX(TEdge e)786 private void SwapX(TEdge e) 787 { 788 //swap horizontal edges' top and bottom x's so they follow the natural 789 //progression of the bounds - ie so their xbots will align with the 790 //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] 791 e.xcurr = e.xtop; 792 e.xtop = e.xbot; 793 e.xbot = e.xcurr; 794 } 795 //------------------------------------------------------------------------------ 796 Reset()797 protected virtual void Reset() 798 { 799 m_CurrentLM = m_MinimaList; 800 801 //reset all edges ... 802 LocalMinima lm = m_MinimaList; 803 while (lm != null) 804 { 805 TEdge e = lm.leftBound; 806 while (e != null) 807 { 808 e.xcurr = e.xbot; 809 e.ycurr = e.ybot; 810 e.side = EdgeSide.esLeft; 811 e.outIdx = -1; 812 e = e.nextInLML; 813 } 814 e = lm.rightBound; 815 while (e != null) 816 { 817 e.xcurr = e.xbot; 818 e.ycurr = e.ybot; 819 e.side = EdgeSide.esRight; 820 e.outIdx = -1; 821 e = e.nextInLML; 822 } 823 lm = lm.next; 824 } 825 return; 826 } 827 //------------------------------------------------------------------------------ 828 GetBounds()829 public IntRect GetBounds() 830 { 831 IntRect result = new IntRect(); 832 LocalMinima lm = m_MinimaList; 833 if (lm == null) return result; 834 result.left = lm.leftBound.xbot; 835 result.top = lm.leftBound.ybot; 836 result.right = lm.leftBound.xbot; 837 result.bottom = lm.leftBound.ybot; 838 while (lm != null) 839 { 840 if (lm.leftBound.ybot > result.bottom) 841 result.bottom = lm.leftBound.ybot; 842 TEdge e = lm.leftBound; 843 for (; ; ) 844 { 845 TEdge bottomE = e; 846 while (e.nextInLML != null) 847 { 848 if (e.xbot < result.left) result.left = e.xbot; 849 if (e.xbot > result.right) result.right = e.xbot; 850 e = e.nextInLML; 851 } 852 if (e.xbot < result.left) result.left = e.xbot; 853 if (e.xbot > result.right) result.right = e.xbot; 854 if (e.xtop < result.left) result.left = e.xtop; 855 if (e.xtop > result.right) result.right = e.xtop; 856 if (e.ytop < result.top) result.top = e.ytop; 857 858 if (bottomE == lm.leftBound) e = lm.rightBound; 859 else break; 860 } 861 lm = lm.next; 862 } 863 return result; 864 } 865 866 } //ClipperBase 867 868 public class Clipper : ClipperBase 869 { 870 private List<OutRec> m_PolyOuts; 871 private ClipType m_ClipType; 872 private Scanbeam m_Scanbeam; 873 private TEdge m_ActiveEdges; 874 private TEdge m_SortedEdges; 875 private IntersectNode m_IntersectNodes; 876 private bool m_ExecuteLocked; 877 private PolyFillType m_ClipFillType; 878 private PolyFillType m_SubjFillType; 879 private List<JoinRec> m_Joins; 880 private List<HorzJoinRec> m_HorizJoins; 881 private bool m_ReverseOutput; 882 Clipper()883 public Clipper() 884 { 885 m_Scanbeam = null; 886 m_ActiveEdges = null; 887 m_SortedEdges = null; 888 m_IntersectNodes = null; 889 m_ExecuteLocked = false; 890 m_PolyOuts = new List<OutRec>(); 891 m_Joins = new List<JoinRec>(); 892 m_HorizJoins = new List<HorzJoinRec>(); 893 m_ReverseOutput = false; 894 } 895 //------------------------------------------------------------------------------ 896 897 //destructor - commented out since I gather this impedes the GC 898 //~Clipper() //destructor 899 //{ 900 // Clear(); 901 // DisposeScanbeamList(); 902 //} 903 //------------------------------------------------------------------------------ 904 Clear()905 public override void Clear() 906 { 907 if (m_edges.Count == 0) return; //avoids problems with ClipperBase destructor 908 DisposeAllPolyPts(); 909 base.Clear(); 910 } 911 //------------------------------------------------------------------------------ 912 DisposeScanbeamList()913 void DisposeScanbeamList() 914 { 915 while ( m_Scanbeam != null ) { 916 Scanbeam sb2 = m_Scanbeam.next; 917 m_Scanbeam = null; 918 m_Scanbeam = sb2; 919 } 920 } 921 //------------------------------------------------------------------------------ 922 Reset()923 protected override void Reset() 924 { 925 base.Reset(); 926 m_Scanbeam = null; 927 m_ActiveEdges = null; 928 m_SortedEdges = null; 929 DisposeAllPolyPts(); 930 LocalMinima lm = m_MinimaList; 931 while (lm != null) 932 { 933 InsertScanbeam(lm.Y); 934 InsertScanbeam(lm.leftBound.ytop); 935 lm = lm.next; 936 } 937 } 938 //------------------------------------------------------------------------------ 939 940 public bool ReverseSolution 941 { 942 get { return m_ReverseOutput; } 943 set { m_ReverseOutput = value; } 944 } 945 //------------------------------------------------------------------------------ 946 InsertScanbeam(Int64 Y)947 private void InsertScanbeam(Int64 Y) 948 { 949 if( m_Scanbeam == null ) 950 { 951 m_Scanbeam = new Scanbeam(); 952 m_Scanbeam.next = null; 953 m_Scanbeam.Y = Y; 954 } 955 else if( Y > m_Scanbeam.Y ) 956 { 957 Scanbeam newSb = new Scanbeam(); 958 newSb.Y = Y; 959 newSb.next = m_Scanbeam; 960 m_Scanbeam = newSb; 961 } else 962 { 963 Scanbeam sb2 = m_Scanbeam; 964 while( sb2.next != null && ( Y <= sb2.next.Y ) ) sb2 = sb2.next; 965 if( Y == sb2.Y ) return; //ie ignores duplicates 966 Scanbeam newSb = new Scanbeam(); 967 newSb.Y = Y; 968 newSb.next = sb2.next; 969 sb2.next = newSb; 970 } 971 } 972 //------------------------------------------------------------------------------ 973 Execute(ClipType clipType, Polygons solution, PolyFillType subjFillType, PolyFillType clipFillType)974 public bool Execute(ClipType clipType, Polygons solution, 975 PolyFillType subjFillType, PolyFillType clipFillType) 976 { 977 if (m_ExecuteLocked) return false; 978 m_ExecuteLocked = true; 979 solution.Clear(); 980 m_SubjFillType = subjFillType; 981 m_ClipFillType = clipFillType; 982 m_ClipType = clipType; 983 bool succeeded = ExecuteInternal(false); 984 //build the return polygons ... 985 if (succeeded) BuildResult(solution); 986 m_ExecuteLocked = false; 987 return succeeded; 988 } 989 //------------------------------------------------------------------------------ 990 Execute(ClipType clipType, ExPolygons solution, PolyFillType subjFillType, PolyFillType clipFillType)991 public bool Execute(ClipType clipType, ExPolygons solution, 992 PolyFillType subjFillType, PolyFillType clipFillType) 993 { 994 if (m_ExecuteLocked) return false; 995 m_ExecuteLocked = true; 996 solution.Clear(); 997 m_SubjFillType = subjFillType; 998 m_ClipFillType = clipFillType; 999 m_ClipType = clipType; 1000 bool succeeded = ExecuteInternal(true); 1001 //build the return polygons ... 1002 if (succeeded) BuildResultEx(solution); 1003 m_ExecuteLocked = false; 1004 return succeeded; 1005 } 1006 //------------------------------------------------------------------------------ 1007 Execute(ClipType clipType, Polygons solution)1008 public bool Execute(ClipType clipType, Polygons solution) 1009 { 1010 return Execute(clipType, solution, 1011 PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); 1012 } 1013 //------------------------------------------------------------------------------ 1014 Execute(ClipType clipType, ExPolygons solution)1015 public bool Execute(ClipType clipType, ExPolygons solution) 1016 { 1017 return Execute(clipType, solution, 1018 PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); 1019 } 1020 //------------------------------------------------------------------------------ 1021 PolySort(OutRec or1, OutRec or2)1022 internal int PolySort(OutRec or1, OutRec or2) 1023 { 1024 if (or1 == or2) return 0; 1025 else if (or1.pts == null || or2.pts == null) 1026 { 1027 if ((or1.pts == null) != (or2.pts == null)) 1028 { 1029 return or1.pts == null ? 1 : -1; 1030 } 1031 else return 0; 1032 } 1033 int i1, i2; 1034 if (or1.isHole) 1035 i1 = or1.FirstLeft.idx; else 1036 i1 = or1.idx; 1037 if (or2.isHole) 1038 i2 = or2.FirstLeft.idx; else 1039 i2 = or2.idx; 1040 int result = i1 - i2; 1041 if (result == 0 && (or1.isHole != or2.isHole)) 1042 { 1043 return or1.isHole ? 1 : -1; 1044 } 1045 return result; 1046 } 1047 //------------------------------------------------------------------------------ 1048 FindAppendLinkEnd(OutRec outRec)1049 internal OutRec FindAppendLinkEnd(OutRec outRec) 1050 { 1051 while (outRec.AppendLink != null) outRec = outRec.AppendLink; 1052 return outRec; 1053 } 1054 //------------------------------------------------------------------------------ 1055 FixHoleLinkage(OutRec outRec)1056 internal void FixHoleLinkage(OutRec outRec) 1057 { 1058 OutRec tmp; 1059 if (outRec.bottomPt != null) 1060 tmp = m_PolyOuts[outRec.bottomPt.idx].FirstLeft; 1061 else 1062 tmp = outRec.FirstLeft; 1063 if (outRec == tmp) throw new ClipperException("HoleLinkage error"); 1064 1065 if (tmp != null) 1066 { 1067 if (tmp.AppendLink != null) tmp = FindAppendLinkEnd(tmp); 1068 1069 if (tmp == outRec) tmp = null; 1070 else if (tmp.isHole) 1071 { 1072 FixHoleLinkage(tmp); 1073 tmp = tmp.FirstLeft; 1074 } 1075 } 1076 outRec.FirstLeft = tmp; 1077 if (tmp == null) outRec.isHole = false; 1078 outRec.AppendLink = null; 1079 } 1080 //------------------------------------------------------------------------------ 1081 ExecuteInternal(bool fixHoleLinkages)1082 private bool ExecuteInternal(bool fixHoleLinkages) 1083 { 1084 bool succeeded; 1085 try 1086 { 1087 Reset(); 1088 if (m_CurrentLM == null) return true; 1089 Int64 botY = PopScanbeam(); 1090 do 1091 { 1092 InsertLocalMinimaIntoAEL(botY); 1093 m_HorizJoins.Clear(); 1094 ProcessHorizontals(); 1095 Int64 topY = PopScanbeam(); 1096 succeeded = ProcessIntersections(botY, topY); 1097 if (!succeeded) break; 1098 ProcessEdgesAtTopOfScanbeam(topY); 1099 botY = topY; 1100 } while (m_Scanbeam != null); 1101 } 1102 catch { succeeded = false; } 1103 1104 if (succeeded) 1105 { 1106 //tidy up output polygons and fix orientations where necessary ... 1107 foreach (OutRec outRec in m_PolyOuts) 1108 { 1109 if (outRec.pts == null) continue; 1110 FixupOutPolygon(outRec); 1111 if (outRec.pts == null) continue; 1112 if (outRec.isHole && fixHoleLinkages) FixHoleLinkage(outRec); 1113 1114 if (outRec.bottomPt == outRec.bottomFlag && 1115 (Orientation(outRec, m_UseFullRange) != (Area(outRec, m_UseFullRange) > 0))) 1116 { 1117 DisposeBottomPt(outRec); 1118 FixupOutPolygon(outRec); 1119 } 1120 1121 if (outRec.isHole == (m_ReverseOutput ^ Orientation(outRec, m_UseFullRange))) 1122 ReversePolyPtLinks(outRec.pts); 1123 } 1124 1125 JoinCommonEdges(fixHoleLinkages); 1126 if (fixHoleLinkages) m_PolyOuts.Sort(new Comparison<OutRec>(PolySort)); 1127 } 1128 m_Joins.Clear(); 1129 m_HorizJoins.Clear(); 1130 return succeeded; 1131 } 1132 //------------------------------------------------------------------------------ 1133 PopScanbeam()1134 private Int64 PopScanbeam() 1135 { 1136 Int64 Y = m_Scanbeam.Y; 1137 m_Scanbeam = m_Scanbeam.next; 1138 return Y; 1139 } 1140 //------------------------------------------------------------------------------ 1141 DisposeAllPolyPts()1142 private void DisposeAllPolyPts(){ 1143 for (int i = 0; i < m_PolyOuts.Count; ++i) DisposeOutRec(i); 1144 m_PolyOuts.Clear(); 1145 } 1146 //------------------------------------------------------------------------------ 1147 DisposeBottomPt(OutRec outRec)1148 void DisposeBottomPt(OutRec outRec) 1149 { 1150 OutPt next = outRec.bottomPt.next; 1151 OutPt prev = outRec.bottomPt.prev; 1152 if (outRec.pts == outRec.bottomPt) outRec.pts = next; 1153 outRec.bottomPt = null; 1154 next.prev = prev; 1155 prev.next = next; 1156 outRec.bottomPt = next; 1157 } 1158 //------------------------------------------------------------------------------ 1159 DisposeOutRec(int index)1160 void DisposeOutRec(int index) 1161 { 1162 OutRec outRec = m_PolyOuts[index]; 1163 if (outRec.pts != null) DisposeOutPts(outRec.pts); 1164 outRec = null; 1165 m_PolyOuts[index] = null; 1166 } 1167 //------------------------------------------------------------------------------ 1168 DisposeOutPts(OutPt pp)1169 private void DisposeOutPts(OutPt pp) 1170 { 1171 if (pp == null) return; 1172 pp.prev.next = null; 1173 while (pp != null) 1174 { 1175 pp = pp.next; 1176 } 1177 } 1178 //------------------------------------------------------------------------------ 1179 AddJoin(TEdge e1, TEdge e2, int e1OutIdx, int e2OutIdx)1180 private void AddJoin(TEdge e1, TEdge e2, int e1OutIdx, int e2OutIdx) 1181 { 1182 JoinRec jr = new JoinRec(); 1183 if (e1OutIdx >= 0) 1184 jr.poly1Idx = e1OutIdx; else 1185 jr.poly1Idx = e1.outIdx; 1186 jr.pt1a = new IntPoint(e1.xcurr, e1.ycurr); 1187 jr.pt1b = new IntPoint(e1.xtop, e1.ytop); 1188 if (e2OutIdx >= 0) 1189 jr.poly2Idx = e2OutIdx; else 1190 jr.poly2Idx = e2.outIdx; 1191 jr.pt2a = new IntPoint(e2.xcurr, e2.ycurr); 1192 jr.pt2b = new IntPoint(e2.xtop, e2.ytop); 1193 m_Joins.Add(jr); 1194 } 1195 //------------------------------------------------------------------------------ 1196 AddHorzJoin(TEdge e, int idx)1197 private void AddHorzJoin(TEdge e, int idx) 1198 { 1199 HorzJoinRec hj = new HorzJoinRec(); 1200 hj.edge = e; 1201 hj.savedIdx = idx; 1202 m_HorizJoins.Add(hj); 1203 } 1204 //------------------------------------------------------------------------------ 1205 InsertLocalMinimaIntoAEL(Int64 botY)1206 private void InsertLocalMinimaIntoAEL(Int64 botY) 1207 { 1208 while( m_CurrentLM != null && ( m_CurrentLM.Y == botY ) ) 1209 { 1210 TEdge lb = m_CurrentLM.leftBound; 1211 TEdge rb = m_CurrentLM.rightBound; 1212 1213 InsertEdgeIntoAEL( lb ); 1214 InsertScanbeam( lb.ytop ); 1215 InsertEdgeIntoAEL( rb ); 1216 1217 if (IsEvenOddFillType(lb)) 1218 { 1219 lb.windDelta = 1; 1220 rb.windDelta = 1; 1221 } 1222 else 1223 { 1224 rb.windDelta = -lb.windDelta; 1225 } 1226 SetWindingCount(lb); 1227 rb.windCnt = lb.windCnt; 1228 rb.windCnt2 = lb.windCnt2; 1229 1230 if( rb.dx == horizontal ) 1231 { 1232 //nb: only rightbounds can have a horizontal bottom edge 1233 AddEdgeToSEL( rb ); 1234 InsertScanbeam( rb.nextInLML.ytop ); 1235 } 1236 else 1237 InsertScanbeam( rb.ytop ); 1238 1239 if( IsContributing(lb) ) 1240 AddLocalMinPoly(lb, rb, new IntPoint(lb.xcurr, m_CurrentLM.Y)); 1241 1242 //if any output polygons share an edge, they'll need joining later ... 1243 if (rb.outIdx >= 0) 1244 { 1245 if (rb.dx == horizontal) 1246 { 1247 for (int i = 0; i < m_HorizJoins.Count; i++) 1248 { 1249 IntPoint pt = new IntPoint(), pt2 = new IntPoint(); //used as dummy params. 1250 HorzJoinRec hj = m_HorizJoins[i]; 1251 //if horizontals rb and hj.edge overlap, flag for joining later ... 1252 if (GetOverlapSegment(new IntPoint(hj.edge.xbot, hj.edge.ybot), 1253 new IntPoint(hj.edge.xtop, hj.edge.ytop), 1254 new IntPoint(rb.xbot, rb.ybot), 1255 new IntPoint(rb.xtop, rb.ytop), 1256 ref pt, ref pt2)) 1257 AddJoin(hj.edge, rb, hj.savedIdx, -1); 1258 } 1259 } 1260 } 1261 1262 1263 if( lb.nextInAEL != rb ) 1264 { 1265 if (rb.outIdx >= 0 && rb.prevInAEL.outIdx >= 0 && 1266 SlopesEqual(rb.prevInAEL, rb, m_UseFullRange)) 1267 AddJoin(rb, rb.prevInAEL, -1, -1); 1268 1269 TEdge e = lb.nextInAEL; 1270 IntPoint pt = new IntPoint(lb.xcurr, lb.ycurr); 1271 while( e != rb ) 1272 { 1273 if(e == null) 1274 throw new ClipperException("InsertLocalMinimaIntoAEL: missing rightbound!"); 1275 //nb: For calculating winding counts etc, IntersectEdges() assumes 1276 //that param1 will be to the right of param2 ABOVE the intersection ... 1277 IntersectEdges( rb , e , pt , Protects.ipNone); //order important here 1278 e = e.nextInAEL; 1279 } 1280 } 1281 PopLocalMinima(); 1282 } 1283 } 1284 //------------------------------------------------------------------------------ 1285 InsertEdgeIntoAEL(TEdge edge)1286 private void InsertEdgeIntoAEL(TEdge edge) 1287 { 1288 edge.prevInAEL = null; 1289 edge.nextInAEL = null; 1290 if (m_ActiveEdges == null) 1291 { 1292 m_ActiveEdges = edge; 1293 } 1294 else if( E2InsertsBeforeE1(m_ActiveEdges, edge) ) 1295 { 1296 edge.nextInAEL = m_ActiveEdges; 1297 m_ActiveEdges.prevInAEL = edge; 1298 m_ActiveEdges = edge; 1299 } else 1300 { 1301 TEdge e = m_ActiveEdges; 1302 while (e.nextInAEL != null && !E2InsertsBeforeE1(e.nextInAEL, edge)) 1303 e = e.nextInAEL; 1304 edge.nextInAEL = e.nextInAEL; 1305 if (e.nextInAEL != null) e.nextInAEL.prevInAEL = edge; 1306 edge.prevInAEL = e; 1307 e.nextInAEL = edge; 1308 } 1309 } 1310 //---------------------------------------------------------------------- 1311 E2InsertsBeforeE1(TEdge e1, TEdge e2)1312 private bool E2InsertsBeforeE1(TEdge e1, TEdge e2) 1313 { 1314 return e2.xcurr == e1.xcurr? e2.dx > e1.dx : e2.xcurr < e1.xcurr; 1315 } 1316 //------------------------------------------------------------------------------ 1317 IsEvenOddFillType(TEdge edge)1318 private bool IsEvenOddFillType(TEdge edge) 1319 { 1320 if (edge.polyType == PolyType.ptSubject) 1321 return m_SubjFillType == PolyFillType.pftEvenOdd; 1322 else 1323 return m_ClipFillType == PolyFillType.pftEvenOdd; 1324 } 1325 //------------------------------------------------------------------------------ 1326 IsEvenOddAltFillType(TEdge edge)1327 private bool IsEvenOddAltFillType(TEdge edge) 1328 { 1329 if (edge.polyType == PolyType.ptSubject) 1330 return m_ClipFillType == PolyFillType.pftEvenOdd; 1331 else 1332 return m_SubjFillType == PolyFillType.pftEvenOdd; 1333 } 1334 //------------------------------------------------------------------------------ 1335 IsContributing(TEdge edge)1336 private bool IsContributing(TEdge edge) 1337 { 1338 PolyFillType pft, pft2; 1339 if (edge.polyType == PolyType.ptSubject) 1340 { 1341 pft = m_SubjFillType; 1342 pft2 = m_ClipFillType; 1343 } 1344 else 1345 { 1346 pft = m_ClipFillType; 1347 pft2 = m_SubjFillType; 1348 } 1349 1350 switch (pft) 1351 { 1352 case PolyFillType.pftEvenOdd: 1353 case PolyFillType.pftNonZero: 1354 if (Math.Abs(edge.windCnt) != 1) return false; 1355 break; 1356 case PolyFillType.pftPositive: 1357 if (edge.windCnt != 1) return false; 1358 break; 1359 default: //PolyFillType.pftNegative 1360 if (edge.windCnt != -1) return false; 1361 break; 1362 } 1363 1364 switch (m_ClipType) 1365 { 1366 case ClipType.ctIntersection: 1367 switch (pft2) 1368 { 1369 case PolyFillType.pftEvenOdd: 1370 case PolyFillType.pftNonZero: 1371 return (edge.windCnt2 != 0); 1372 case PolyFillType.pftPositive: 1373 return (edge.windCnt2 > 0); 1374 default: 1375 return (edge.windCnt2 < 0); 1376 } 1377 case ClipType.ctUnion: 1378 switch (pft2) 1379 { 1380 case PolyFillType.pftEvenOdd: 1381 case PolyFillType.pftNonZero: 1382 return (edge.windCnt2 == 0); 1383 case PolyFillType.pftPositive: 1384 return (edge.windCnt2 <= 0); 1385 default: 1386 return (edge.windCnt2 >= 0); 1387 } 1388 case ClipType.ctDifference: 1389 if (edge.polyType == PolyType.ptSubject) 1390 switch (pft2) 1391 { 1392 case PolyFillType.pftEvenOdd: 1393 case PolyFillType.pftNonZero: 1394 return (edge.windCnt2 == 0); 1395 case PolyFillType.pftPositive: 1396 return (edge.windCnt2 <= 0); 1397 default: 1398 return (edge.windCnt2 >= 0); 1399 } 1400 else 1401 switch (pft2) 1402 { 1403 case PolyFillType.pftEvenOdd: 1404 case PolyFillType.pftNonZero: 1405 return (edge.windCnt2 != 0); 1406 case PolyFillType.pftPositive: 1407 return (edge.windCnt2 > 0); 1408 default: 1409 return (edge.windCnt2 < 0); 1410 } 1411 } 1412 return true; 1413 } 1414 //------------------------------------------------------------------------------ 1415 SetWindingCount(TEdge edge)1416 private void SetWindingCount(TEdge edge) 1417 { 1418 TEdge e = edge.prevInAEL; 1419 //find the edge of the same polytype that immediately preceeds 'edge' in AEL 1420 while (e != null && e.polyType != edge.polyType) 1421 e = e.prevInAEL; 1422 if (e == null) 1423 { 1424 edge.windCnt = edge.windDelta; 1425 edge.windCnt2 = 0; 1426 e = m_ActiveEdges; //ie get ready to calc windCnt2 1427 } 1428 else if (IsEvenOddFillType(edge)) 1429 { 1430 //even-odd filling ... 1431 edge.windCnt = 1; 1432 edge.windCnt2 = e.windCnt2; 1433 e = e.nextInAEL; //ie get ready to calc windCnt2 1434 } 1435 else 1436 { 1437 //nonZero filling ... 1438 if (e.windCnt * e.windDelta < 0) 1439 { 1440 if (Math.Abs(e.windCnt) > 1) 1441 { 1442 if (e.windDelta * edge.windDelta < 0) 1443 edge.windCnt = e.windCnt; 1444 else 1445 edge.windCnt = e.windCnt + edge.windDelta; 1446 } 1447 else 1448 edge.windCnt = e.windCnt + e.windDelta + edge.windDelta; 1449 } 1450 else 1451 { 1452 if (Math.Abs(e.windCnt) > 1 && e.windDelta * edge.windDelta < 0) 1453 edge.windCnt = e.windCnt; 1454 else if (e.windCnt + edge.windDelta == 0) 1455 edge.windCnt = e.windCnt; 1456 else 1457 edge.windCnt = e.windCnt + edge.windDelta; 1458 } 1459 edge.windCnt2 = e.windCnt2; 1460 e = e.nextInAEL; //ie get ready to calc windCnt2 1461 } 1462 1463 //update windCnt2 ... 1464 if (IsEvenOddAltFillType(edge)) 1465 { 1466 //even-odd filling ... 1467 while (e != edge) 1468 { 1469 edge.windCnt2 = (edge.windCnt2 == 0) ? 1 : 0; 1470 e = e.nextInAEL; 1471 } 1472 } 1473 else 1474 { 1475 //nonZero filling ... 1476 while (e != edge) 1477 { 1478 edge.windCnt2 += e.windDelta; 1479 e = e.nextInAEL; 1480 } 1481 } 1482 } 1483 //------------------------------------------------------------------------------ 1484 AddEdgeToSEL(TEdge edge)1485 private void AddEdgeToSEL(TEdge edge) 1486 { 1487 //SEL pointers in PEdge are reused to build a list of horizontal edges. 1488 //However, we don't need to worry about order with horizontal edge processing. 1489 if (m_SortedEdges == null) 1490 { 1491 m_SortedEdges = edge; 1492 edge.prevInSEL = null; 1493 edge.nextInSEL = null; 1494 } 1495 else 1496 { 1497 edge.nextInSEL = m_SortedEdges; 1498 edge.prevInSEL = null; 1499 m_SortedEdges.prevInSEL = edge; 1500 m_SortedEdges = edge; 1501 } 1502 } 1503 //------------------------------------------------------------------------------ 1504 CopyAELToSEL()1505 private void CopyAELToSEL() 1506 { 1507 TEdge e = m_ActiveEdges; 1508 m_SortedEdges = e; 1509 if (m_ActiveEdges == null) 1510 return; 1511 m_SortedEdges.prevInSEL = null; 1512 e = e.nextInAEL; 1513 while (e != null) 1514 { 1515 e.prevInSEL = e.prevInAEL; 1516 e.prevInSEL.nextInSEL = e; 1517 e.nextInSEL = null; 1518 e = e.nextInAEL; 1519 } 1520 } 1521 //------------------------------------------------------------------------------ 1522 SwapPositionsInAEL(TEdge edge1, TEdge edge2)1523 private void SwapPositionsInAEL(TEdge edge1, TEdge edge2) 1524 { 1525 if (edge1.nextInAEL == null && edge1.prevInAEL == null) 1526 return; 1527 if (edge2.nextInAEL == null && edge2.prevInAEL == null) 1528 return; 1529 1530 if (edge1.nextInAEL == edge2) 1531 { 1532 TEdge next = edge2.nextInAEL; 1533 if (next != null) 1534 next.prevInAEL = edge1; 1535 TEdge prev = edge1.prevInAEL; 1536 if (prev != null) 1537 prev.nextInAEL = edge2; 1538 edge2.prevInAEL = prev; 1539 edge2.nextInAEL = edge1; 1540 edge1.prevInAEL = edge2; 1541 edge1.nextInAEL = next; 1542 } 1543 else if (edge2.nextInAEL == edge1) 1544 { 1545 TEdge next = edge1.nextInAEL; 1546 if (next != null) 1547 next.prevInAEL = edge2; 1548 TEdge prev = edge2.prevInAEL; 1549 if (prev != null) 1550 prev.nextInAEL = edge1; 1551 edge1.prevInAEL = prev; 1552 edge1.nextInAEL = edge2; 1553 edge2.prevInAEL = edge1; 1554 edge2.nextInAEL = next; 1555 } 1556 else 1557 { 1558 TEdge next = edge1.nextInAEL; 1559 TEdge prev = edge1.prevInAEL; 1560 edge1.nextInAEL = edge2.nextInAEL; 1561 if (edge1.nextInAEL != null) 1562 edge1.nextInAEL.prevInAEL = edge1; 1563 edge1.prevInAEL = edge2.prevInAEL; 1564 if (edge1.prevInAEL != null) 1565 edge1.prevInAEL.nextInAEL = edge1; 1566 edge2.nextInAEL = next; 1567 if (edge2.nextInAEL != null) 1568 edge2.nextInAEL.prevInAEL = edge2; 1569 edge2.prevInAEL = prev; 1570 if (edge2.prevInAEL != null) 1571 edge2.prevInAEL.nextInAEL = edge2; 1572 } 1573 1574 if (edge1.prevInAEL == null) 1575 m_ActiveEdges = edge1; 1576 else if (edge2.prevInAEL == null) 1577 m_ActiveEdges = edge2; 1578 } 1579 //------------------------------------------------------------------------------ 1580 SwapPositionsInSEL(TEdge edge1, TEdge edge2)1581 private void SwapPositionsInSEL(TEdge edge1, TEdge edge2) 1582 { 1583 if (edge1.nextInSEL == null && edge1.prevInSEL == null) 1584 return; 1585 if (edge2.nextInSEL == null && edge2.prevInSEL == null) 1586 return; 1587 1588 if (edge1.nextInSEL == edge2) 1589 { 1590 TEdge next = edge2.nextInSEL; 1591 if (next != null) 1592 next.prevInSEL = edge1; 1593 TEdge prev = edge1.prevInSEL; 1594 if (prev != null) 1595 prev.nextInSEL = edge2; 1596 edge2.prevInSEL = prev; 1597 edge2.nextInSEL = edge1; 1598 edge1.prevInSEL = edge2; 1599 edge1.nextInSEL = next; 1600 } 1601 else if (edge2.nextInSEL == edge1) 1602 { 1603 TEdge next = edge1.nextInSEL; 1604 if (next != null) 1605 next.prevInSEL = edge2; 1606 TEdge prev = edge2.prevInSEL; 1607 if (prev != null) 1608 prev.nextInSEL = edge1; 1609 edge1.prevInSEL = prev; 1610 edge1.nextInSEL = edge2; 1611 edge2.prevInSEL = edge1; 1612 edge2.nextInSEL = next; 1613 } 1614 else 1615 { 1616 TEdge next = edge1.nextInSEL; 1617 TEdge prev = edge1.prevInSEL; 1618 edge1.nextInSEL = edge2.nextInSEL; 1619 if (edge1.nextInSEL != null) 1620 edge1.nextInSEL.prevInSEL = edge1; 1621 edge1.prevInSEL = edge2.prevInSEL; 1622 if (edge1.prevInSEL != null) 1623 edge1.prevInSEL.nextInSEL = edge1; 1624 edge2.nextInSEL = next; 1625 if (edge2.nextInSEL != null) 1626 edge2.nextInSEL.prevInSEL = edge2; 1627 edge2.prevInSEL = prev; 1628 if (edge2.prevInSEL != null) 1629 edge2.prevInSEL.nextInSEL = edge2; 1630 } 1631 1632 if (edge1.prevInSEL == null) 1633 m_SortedEdges = edge1; 1634 else if (edge2.prevInSEL == null) 1635 m_SortedEdges = edge2; 1636 } 1637 //------------------------------------------------------------------------------ 1638 1639 AddLocalMaxPoly(TEdge e1, TEdge e2, IntPoint pt)1640 private void AddLocalMaxPoly(TEdge e1, TEdge e2, IntPoint pt) 1641 { 1642 AddOutPt(e1, pt); 1643 if (e1.outIdx == e2.outIdx) 1644 { 1645 e1.outIdx = -1; 1646 e2.outIdx = -1; 1647 } 1648 else if (e1.outIdx < e2.outIdx) 1649 AppendPolygon(e1, e2); 1650 else 1651 AppendPolygon(e2, e1); 1652 } 1653 //------------------------------------------------------------------------------ 1654 AddLocalMinPoly(TEdge e1, TEdge e2, IntPoint pt)1655 private void AddLocalMinPoly(TEdge e1, TEdge e2, IntPoint pt) 1656 { 1657 TEdge e, prevE; 1658 if (e2.dx == horizontal || (e1.dx > e2.dx)) 1659 { 1660 AddOutPt(e1, pt); 1661 e2.outIdx = e1.outIdx; 1662 e1.side = EdgeSide.esLeft; 1663 e2.side = EdgeSide.esRight; 1664 e = e1; 1665 if (e.prevInAEL == e2) 1666 prevE = e2.prevInAEL; 1667 else 1668 prevE = e.prevInAEL; 1669 } 1670 else 1671 { 1672 AddOutPt(e2, pt); 1673 e1.outIdx = e2.outIdx; 1674 e1.side = EdgeSide.esRight; 1675 e2.side = EdgeSide.esLeft; 1676 e = e2; 1677 if (e.prevInAEL == e1) 1678 prevE = e1.prevInAEL; 1679 else 1680 prevE = e.prevInAEL; 1681 } 1682 1683 if (prevE != null && prevE.outIdx >= 0 && 1684 (TopX(prevE, pt.Y) == TopX(e, pt.Y)) && 1685 SlopesEqual(e, prevE, m_UseFullRange)) 1686 AddJoin(e, prevE, -1, -1); 1687 1688 } 1689 //------------------------------------------------------------------------------ 1690 CreateOutRec()1691 private OutRec CreateOutRec() 1692 { 1693 OutRec result = new OutRec(); 1694 result.idx = -1; 1695 result.isHole = false; 1696 result.FirstLeft = null; 1697 result.AppendLink = null; 1698 result.pts = null; 1699 result.bottomPt = null; 1700 result.bottomFlag = null; 1701 result.sides = EdgeSide.esNeither; 1702 return result; 1703 } 1704 //------------------------------------------------------------------------------ 1705 AddOutPt(TEdge e, IntPoint pt)1706 private void AddOutPt(TEdge e, IntPoint pt) 1707 { 1708 bool ToFront = (e.side == EdgeSide.esLeft); 1709 if( e.outIdx < 0 ) 1710 { 1711 OutRec outRec = CreateOutRec(); 1712 m_PolyOuts.Add(outRec); 1713 outRec.idx = m_PolyOuts.Count -1; 1714 e.outIdx = outRec.idx; 1715 OutPt op = new OutPt(); 1716 outRec.pts = op; 1717 outRec.bottomPt = op; 1718 op.pt = pt; 1719 op.idx = outRec.idx; 1720 op.next = op; 1721 op.prev = op; 1722 SetHoleState(e, outRec); 1723 } else 1724 { 1725 OutRec outRec = m_PolyOuts[e.outIdx]; 1726 OutPt op = outRec.pts, op2, opBot; 1727 if (ToFront && PointsEqual(pt, op.pt) || 1728 (!ToFront && PointsEqual(pt, op.prev.pt))) return; 1729 1730 if ((e.side | outRec.sides) != outRec.sides) 1731 { 1732 //check for 'rounding' artefacts ... 1733 if (outRec.sides == EdgeSide.esNeither && pt.Y == op.pt.Y) 1734 if (ToFront) 1735 { 1736 if (pt.X == op.pt.X + 1) return; //ie wrong side of bottomPt 1737 } 1738 else if (pt.X == op.pt.X - 1) return; //ie wrong side of bottomPt 1739 1740 outRec.sides = (EdgeSide)(outRec.sides | e.side); 1741 if (outRec.sides == EdgeSide.esBoth) 1742 { 1743 //A vertex from each side has now been added. 1744 //Vertices of one side of an output polygon are quite commonly close to 1745 //or even 'touching' edges of the other side of the output polygon. 1746 //Very occasionally vertices from one side can 'cross' an edge on the 1747 //the other side. The distance 'crossed' is always less that a unit 1748 //and is purely an artefact of coordinate rounding. Nevertheless, this 1749 //results in very tiny self-intersections. Because of the way 1750 //orientation is calculated, even tiny self-intersections can cause 1751 //the Orientation function to return the wrong result. Therefore, it's 1752 //important to ensure that any self-intersections close to BottomPt are 1753 //detected and removed before orientation is assigned. 1754 1755 if (ToFront) 1756 { 1757 opBot = outRec.pts; 1758 op2 = opBot.next; //op2 == right side 1759 if (opBot.pt.Y != op2.pt.Y && opBot.pt.Y != pt.Y && 1760 ((opBot.pt.X - pt.X) / (opBot.pt.Y - pt.Y) < 1761 (opBot.pt.X - op2.pt.X) / (opBot.pt.Y - op2.pt.Y))) 1762 outRec.bottomFlag = opBot; 1763 } 1764 else 1765 { 1766 opBot = outRec.pts.prev; 1767 op2 = opBot.next; //op2 == left side 1768 if (opBot.pt.Y != op2.pt.Y && opBot.pt.Y != pt.Y && 1769 ((opBot.pt.X - pt.X) / (opBot.pt.Y - pt.Y) > 1770 (opBot.pt.X - op2.pt.X) / (opBot.pt.Y - op2.pt.Y))) 1771 outRec.bottomFlag = opBot; 1772 } 1773 } 1774 } 1775 1776 op2 = new OutPt(); 1777 op2.pt = pt; 1778 op2.idx = outRec.idx; 1779 if (op2.pt.Y == outRec.bottomPt.pt.Y && 1780 op2.pt.X < outRec.bottomPt.pt.X) 1781 outRec.bottomPt = op2; 1782 op2.next = op; 1783 op2.prev = op.prev; 1784 op2.prev.next = op2; 1785 op.prev = op2; 1786 if (ToFront) outRec.pts = op2; 1787 } 1788 } 1789 //------------------------------------------------------------------------------ 1790 SwapPoints(ref IntPoint pt1, ref IntPoint pt2)1791 internal void SwapPoints(ref IntPoint pt1, ref IntPoint pt2) 1792 { 1793 IntPoint tmp = pt1; 1794 pt1 = pt2; 1795 pt2 = tmp; 1796 } 1797 //------------------------------------------------------------------------------ 1798 GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, IntPoint pt2b, ref IntPoint pt1, ref IntPoint pt2)1799 private bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, 1800 IntPoint pt2b, ref IntPoint pt1, ref IntPoint pt2) 1801 { 1802 //precondition: segments are colinear. 1803 if ( pt1a.Y == pt1b.Y || Math.Abs((pt1a.X - pt1b.X)/(pt1a.Y - pt1b.Y)) > 1 ) 1804 { 1805 if (pt1a.X > pt1b.X) SwapPoints(ref pt1a, ref pt1b); 1806 if (pt2a.X > pt2b.X) SwapPoints(ref pt2a, ref pt2b); 1807 if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; 1808 if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; 1809 return pt1.X < pt2.X; 1810 } else 1811 { 1812 if (pt1a.Y < pt1b.Y) SwapPoints(ref pt1a, ref pt1b); 1813 if (pt2a.Y < pt2b.Y) SwapPoints(ref pt2a, ref pt2b); 1814 if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; 1815 if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; 1816 return pt1.Y > pt2.Y; 1817 } 1818 } 1819 //------------------------------------------------------------------------------ 1820 FindSegment(ref OutPt pp, ref IntPoint pt1, ref IntPoint pt2)1821 private bool FindSegment(ref OutPt pp, ref IntPoint pt1, ref IntPoint pt2) 1822 { 1823 if (pp == null) return false; 1824 OutPt pp2 = pp; 1825 IntPoint pt1a = new IntPoint(pt1); 1826 IntPoint pt2a = new IntPoint(pt2); 1827 do 1828 { 1829 if (SlopesEqual(pt1a, pt2a, pp.pt, pp.prev.pt, true) && 1830 SlopesEqual(pt1a, pt2a, pp.pt, true) && 1831 GetOverlapSegment(pt1a, pt2a, pp.pt, pp.prev.pt, ref pt1, ref pt2)) 1832 return true; 1833 pp = pp.next; 1834 } 1835 while (pp != pp2); 1836 return false; 1837 } 1838 //------------------------------------------------------------------------------ 1839 Pt3IsBetweenPt1AndPt2(IntPoint pt1, IntPoint pt2, IntPoint pt3)1840 internal bool Pt3IsBetweenPt1AndPt2(IntPoint pt1, IntPoint pt2, IntPoint pt3) 1841 { 1842 if (PointsEqual(pt1, pt3) || PointsEqual(pt2, pt3)) return true; 1843 else if (pt1.X != pt2.X) return (pt1.X < pt3.X) == (pt3.X < pt2.X); 1844 else return (pt1.Y < pt3.Y) == (pt3.Y < pt2.Y); 1845 } 1846 //------------------------------------------------------------------------------ 1847 InsertPolyPtBetween(OutPt p1, OutPt p2, IntPoint pt)1848 private OutPt InsertPolyPtBetween(OutPt p1, OutPt p2, IntPoint pt) 1849 { 1850 OutPt result = new OutPt(); 1851 result.pt = pt; 1852 if (p2 == p1.next) 1853 { 1854 p1.next = result; 1855 p2.prev = result; 1856 result.next = p2; 1857 result.prev = p1; 1858 } else 1859 { 1860 p2.next = result; 1861 p1.prev = result; 1862 result.next = p1; 1863 result.prev = p2; 1864 } 1865 return result; 1866 } 1867 //------------------------------------------------------------------------------ 1868 SetHoleState(TEdge e, OutRec outRec)1869 private void SetHoleState(TEdge e, OutRec outRec) 1870 { 1871 bool isHole = false; 1872 TEdge e2 = e.prevInAEL; 1873 while (e2 != null) 1874 { 1875 if (e2.outIdx >= 0) 1876 { 1877 isHole = !isHole; 1878 if (outRec.FirstLeft == null) 1879 outRec.FirstLeft = m_PolyOuts[e2.outIdx]; 1880 } 1881 e2 = e2.prevInAEL; 1882 } 1883 if (isHole) outRec.isHole = true; 1884 } 1885 //------------------------------------------------------------------------------ 1886 GetDx(IntPoint pt1, IntPoint pt2)1887 private double GetDx(IntPoint pt1, IntPoint pt2) 1888 { 1889 if (pt1.Y == pt2.Y) return horizontal; 1890 else return (double)(pt2.X - pt1.X) / (double)(pt2.Y - pt1.Y); 1891 } 1892 //--------------------------------------------------------------------------- 1893 FirstIsBottomPt(OutPt btmPt1, OutPt btmPt2)1894 private bool FirstIsBottomPt(OutPt btmPt1, OutPt btmPt2) 1895 { 1896 OutPt p = btmPt1.prev; 1897 while (PointsEqual(p.pt, btmPt1.pt) && (p != btmPt1)) p = p.prev; 1898 double dx1p = Math.Abs(GetDx(btmPt1.pt, p.pt)); 1899 p = btmPt1.next; 1900 while (PointsEqual(p.pt, btmPt1.pt) && (p != btmPt1)) p = p.next; 1901 double dx1n = Math.Abs(GetDx(btmPt1.pt, p.pt)); 1902 1903 p = btmPt2.prev; 1904 while (PointsEqual(p.pt, btmPt2.pt) && (p != btmPt2)) p = p.prev; 1905 double dx2p = Math.Abs(GetDx(btmPt2.pt, p.pt)); 1906 p = btmPt2.next; 1907 while (PointsEqual(p.pt, btmPt2.pt) && (p != btmPt2)) p = p.next; 1908 double dx2n = Math.Abs(GetDx(btmPt2.pt, p.pt)); 1909 return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); 1910 } 1911 //------------------------------------------------------------------------------ 1912 GetBottomPt(OutPt pp)1913 private OutPt GetBottomPt(OutPt pp) 1914 { 1915 OutPt dups = null; 1916 OutPt p = pp.next; 1917 while (p != pp) 1918 { 1919 if (p.pt.Y > pp.pt.Y) 1920 { 1921 pp = p; 1922 dups = null; 1923 } 1924 else if (p.pt.Y == pp.pt.Y && p.pt.X <= pp.pt.X) 1925 { 1926 if (p.pt.X < pp.pt.X) 1927 { 1928 dups = null; 1929 pp = p; 1930 } else 1931 { 1932 if (p.next != pp && p.prev != pp) dups = p; 1933 } 1934 } 1935 p = p.next; 1936 } 1937 if (dups != null) 1938 { 1939 //there appears to be at least 2 vertices at bottomPt so ... 1940 while (dups != p) 1941 { 1942 if (!FirstIsBottomPt(p, dups)) pp = dups; 1943 dups = dups.next; 1944 while (!PointsEqual(dups.pt, pp.pt)) dups = dups.next; 1945 } 1946 } 1947 return pp; 1948 } 1949 //------------------------------------------------------------------------------ 1950 GetLowermostRec(OutRec outRec1, OutRec outRec2)1951 private OutRec GetLowermostRec(OutRec outRec1, OutRec outRec2) 1952 { 1953 //work out which polygon fragment has the correct hole state ... 1954 OutPt bPt1 = outRec1.bottomPt; 1955 OutPt bPt2 = outRec2.bottomPt; 1956 if (bPt1.pt.Y > bPt2.pt.Y) return outRec1; 1957 else if (bPt1.pt.Y < bPt2.pt.Y) return outRec2; 1958 else if (bPt1.pt.X < bPt2.pt.X) return outRec1; 1959 else if (bPt1.pt.X > bPt2.pt.X) return outRec2; 1960 else if (bPt1.next == bPt1) return outRec2; 1961 else if (bPt2.next == bPt2) return outRec1; 1962 else if (FirstIsBottomPt(bPt1, bPt2)) return outRec1; 1963 else return outRec2; 1964 } 1965 //------------------------------------------------------------------------------ 1966 AppendPolygon(TEdge e1, TEdge e2)1967 private void AppendPolygon(TEdge e1, TEdge e2) 1968 { 1969 //get the start and ends of both output polygons ... 1970 OutRec outRec1 = m_PolyOuts[e1.outIdx]; 1971 OutRec outRec2 = m_PolyOuts[e2.outIdx]; 1972 1973 OutRec holeStateRec; 1974 if (outRec1.FirstLeft == outRec2) holeStateRec = outRec2; 1975 else if (outRec2.FirstLeft == outRec1) holeStateRec = outRec1; 1976 else holeStateRec = GetLowermostRec(outRec1, outRec2); 1977 1978 OutPt p1_lft = outRec1.pts; 1979 OutPt p1_rt = p1_lft.prev; 1980 OutPt p2_lft = outRec2.pts; 1981 OutPt p2_rt = p2_lft.prev; 1982 1983 EdgeSide side; 1984 //join e2 poly onto e1 poly and delete pointers to e2 ... 1985 if( e1.side == EdgeSide.esLeft ) 1986 { 1987 if (e2.side == EdgeSide.esLeft) 1988 { 1989 //z y x a b c 1990 ReversePolyPtLinks(p2_lft); 1991 p2_lft.next = p1_lft; 1992 p1_lft.prev = p2_lft; 1993 p1_rt.next = p2_rt; 1994 p2_rt.prev = p1_rt; 1995 outRec1.pts = p2_rt; 1996 } else 1997 { 1998 //x y z a b c 1999 p2_rt.next = p1_lft; 2000 p1_lft.prev = p2_rt; 2001 p2_lft.prev = p1_rt; 2002 p1_rt.next = p2_lft; 2003 outRec1.pts = p2_lft; 2004 } 2005 side = EdgeSide.esLeft; 2006 } else 2007 { 2008 if (e2.side == EdgeSide.esRight) 2009 { 2010 //a b c z y x 2011 ReversePolyPtLinks( p2_lft ); 2012 p1_rt.next = p2_rt; 2013 p2_rt.prev = p1_rt; 2014 p2_lft.next = p1_lft; 2015 p1_lft.prev = p2_lft; 2016 } else 2017 { 2018 //a b c x y z 2019 p1_rt.next = p2_lft; 2020 p2_lft.prev = p1_rt; 2021 p1_lft.prev = p2_rt; 2022 p2_rt.next = p1_lft; 2023 } 2024 side = EdgeSide.esRight; 2025 } 2026 2027 if (holeStateRec == outRec2) 2028 { 2029 outRec1.bottomPt = outRec2.bottomPt; 2030 outRec1.bottomPt.idx = outRec1.idx; 2031 if (outRec2.FirstLeft != outRec1) 2032 outRec1.FirstLeft = outRec2.FirstLeft; 2033 outRec1.isHole = outRec2.isHole; 2034 } 2035 outRec2.pts = null; 2036 outRec2.bottomPt = null; 2037 outRec2.AppendLink = outRec1; 2038 int OKIdx = e1.outIdx; 2039 int ObsoleteIdx = e2.outIdx; 2040 2041 e1.outIdx = -1; //nb: safe because we only get here via AddLocalMaxPoly 2042 e2.outIdx = -1; 2043 2044 TEdge e = m_ActiveEdges; 2045 while( e != null ) 2046 { 2047 if( e.outIdx == ObsoleteIdx ) 2048 { 2049 e.outIdx = OKIdx; 2050 e.side = side; 2051 break; 2052 } 2053 e = e.nextInAEL; 2054 } 2055 2056 2057 for (int i = 0; i < m_Joins.Count; ++i) 2058 { 2059 if (m_Joins[i].poly1Idx == ObsoleteIdx) m_Joins[i].poly1Idx = OKIdx; 2060 if (m_Joins[i].poly2Idx == ObsoleteIdx) m_Joins[i].poly2Idx = OKIdx; 2061 } 2062 2063 for (int i = 0; i < m_HorizJoins.Count; ++i) 2064 { 2065 if (m_HorizJoins[i].savedIdx == ObsoleteIdx) 2066 m_HorizJoins[i].savedIdx = OKIdx; 2067 } 2068 2069 } 2070 //------------------------------------------------------------------------------ 2071 ReversePolyPtLinks(OutPt pp)2072 private void ReversePolyPtLinks(OutPt pp) 2073 { 2074 OutPt pp1; 2075 OutPt pp2; 2076 pp1 = pp; 2077 do 2078 { 2079 pp2 = pp1.next; 2080 pp1.next = pp1.prev; 2081 pp1.prev = pp2; 2082 pp1 = pp2; 2083 } while (pp1 != pp); 2084 } 2085 //------------------------------------------------------------------------------ 2086 SwapSides(TEdge edge1, TEdge edge2)2087 private static void SwapSides(TEdge edge1, TEdge edge2) 2088 { 2089 EdgeSide side = edge1.side; 2090 edge1.side = edge2.side; 2091 edge2.side = side; 2092 } 2093 //------------------------------------------------------------------------------ 2094 SwapPolyIndexes(TEdge edge1, TEdge edge2)2095 private static void SwapPolyIndexes(TEdge edge1, TEdge edge2) 2096 { 2097 int outIdx = edge1.outIdx; 2098 edge1.outIdx = edge2.outIdx; 2099 edge2.outIdx = outIdx; 2100 } 2101 //------------------------------------------------------------------------------ 2102 DoEdge1(TEdge edge1, TEdge edge2, IntPoint pt)2103 private void DoEdge1(TEdge edge1, TEdge edge2, IntPoint pt) 2104 { 2105 AddOutPt(edge1, pt); 2106 SwapSides(edge1, edge2); 2107 SwapPolyIndexes(edge1, edge2); 2108 } 2109 //------------------------------------------------------------------------------ 2110 DoEdge2(TEdge edge1, TEdge edge2, IntPoint pt)2111 private void DoEdge2(TEdge edge1, TEdge edge2, IntPoint pt) 2112 { 2113 AddOutPt(edge2, pt); 2114 SwapSides(edge1, edge2); 2115 SwapPolyIndexes(edge1, edge2); 2116 } 2117 //------------------------------------------------------------------------------ 2118 DoBothEdges(TEdge edge1, TEdge edge2, IntPoint pt)2119 private void DoBothEdges(TEdge edge1, TEdge edge2, IntPoint pt) 2120 { 2121 AddOutPt(edge1, pt); 2122 AddOutPt(edge2, pt); 2123 SwapSides(edge1, edge2); 2124 SwapPolyIndexes(edge1, edge2); 2125 } 2126 //------------------------------------------------------------------------------ 2127 IntersectEdges(TEdge e1, TEdge e2, IntPoint pt, Protects protects)2128 private void IntersectEdges(TEdge e1, TEdge e2, IntPoint pt, Protects protects) 2129 { 2130 //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before 2131 //e2 in AEL except when e1 is being inserted at the intersection point ... 2132 2133 bool e1stops = (Protects.ipLeft & protects) == 0 && e1.nextInLML == null && 2134 e1.xtop == pt.X && e1.ytop == pt.Y; 2135 bool e2stops = (Protects.ipRight & protects) == 0 && e2.nextInLML == null && 2136 e2.xtop == pt.X && e2.ytop == pt.Y; 2137 bool e1Contributing = (e1.outIdx >= 0); 2138 bool e2contributing = (e2.outIdx >= 0); 2139 2140 //update winding counts... 2141 //assumes that e1 will be to the right of e2 ABOVE the intersection 2142 if (e1.polyType == e2.polyType) 2143 { 2144 if (IsEvenOddFillType(e1)) 2145 { 2146 int oldE1WindCnt = e1.windCnt; 2147 e1.windCnt = e2.windCnt; 2148 e2.windCnt = oldE1WindCnt; 2149 } 2150 else 2151 { 2152 if (e1.windCnt + e2.windDelta == 0) e1.windCnt = -e1.windCnt; 2153 else e1.windCnt += e2.windDelta; 2154 if (e2.windCnt - e1.windDelta == 0) e2.windCnt = -e2.windCnt; 2155 else e2.windCnt -= e1.windDelta; 2156 } 2157 } 2158 else 2159 { 2160 if (!IsEvenOddFillType(e2)) e1.windCnt2 += e2.windDelta; 2161 else e1.windCnt2 = (e1.windCnt2 == 0) ? 1 : 0; 2162 if (!IsEvenOddFillType(e1)) e2.windCnt2 -= e1.windDelta; 2163 else e2.windCnt2 = (e2.windCnt2 == 0) ? 1 : 0; 2164 } 2165 2166 PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; 2167 if (e1.polyType == PolyType.ptSubject) 2168 { 2169 e1FillType = m_SubjFillType; 2170 e1FillType2 = m_ClipFillType; 2171 } 2172 else 2173 { 2174 e1FillType = m_ClipFillType; 2175 e1FillType2 = m_SubjFillType; 2176 } 2177 if (e2.polyType == PolyType.ptSubject) 2178 { 2179 e2FillType = m_SubjFillType; 2180 e2FillType2 = m_ClipFillType; 2181 } 2182 else 2183 { 2184 e2FillType = m_ClipFillType; 2185 e2FillType2 = m_SubjFillType; 2186 } 2187 2188 int e1Wc, e2Wc; 2189 switch (e1FillType) 2190 { 2191 case PolyFillType.pftPositive: e1Wc = e1.windCnt; break; 2192 case PolyFillType.pftNegative: e1Wc = -e1.windCnt; break; 2193 default: e1Wc = Math.Abs(e1.windCnt); break; 2194 } 2195 switch (e2FillType) 2196 { 2197 case PolyFillType.pftPositive: e2Wc = e2.windCnt; break; 2198 case PolyFillType.pftNegative: e2Wc = -e2.windCnt; break; 2199 default: e2Wc = Math.Abs(e2.windCnt); break; 2200 } 2201 2202 2203 if (e1Contributing && e2contributing) 2204 { 2205 if ( e1stops || e2stops || 2206 (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || 2207 (e1.polyType != e2.polyType && m_ClipType != ClipType.ctXor)) 2208 AddLocalMaxPoly(e1, e2, pt); 2209 else 2210 DoBothEdges(e1, e2, pt); 2211 } 2212 else if (e1Contributing) 2213 { 2214 if ((e2Wc == 0 || e2Wc == 1) && 2215 (m_ClipType != ClipType.ctIntersection || 2216 e2.polyType == PolyType.ptSubject || (e2.windCnt2 != 0))) 2217 DoEdge1(e1, e2, pt); 2218 } 2219 else if (e2contributing) 2220 { 2221 if ((e1Wc == 0 || e1Wc == 1) && 2222 (m_ClipType != ClipType.ctIntersection || 2223 e1.polyType == PolyType.ptSubject || (e1.windCnt2 != 0))) 2224 DoEdge2(e1, e2, pt); 2225 } 2226 else if ( (e1Wc == 0 || e1Wc == 1) && 2227 (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) 2228 { 2229 //neither edge is currently contributing ... 2230 Int64 e1Wc2, e2Wc2; 2231 switch (e1FillType2) 2232 { 2233 case PolyFillType.pftPositive: e1Wc2 = e1.windCnt2; break; 2234 case PolyFillType.pftNegative: e1Wc2 = -e1.windCnt2; break; 2235 default: e1Wc2 = Math.Abs(e1.windCnt2); break; 2236 } 2237 switch (e2FillType2) 2238 { 2239 case PolyFillType.pftPositive: e2Wc2 = e2.windCnt2; break; 2240 case PolyFillType.pftNegative: e2Wc2 = -e2.windCnt2; break; 2241 default: e2Wc2 = Math.Abs(e2.windCnt2); break; 2242 } 2243 2244 if (e1.polyType != e2.polyType) 2245 AddLocalMinPoly(e1, e2, pt); 2246 else if (e1Wc == 1 && e2Wc == 1) 2247 switch (m_ClipType) 2248 { 2249 case ClipType.ctIntersection: 2250 { 2251 if (e1Wc2 > 0 && e2Wc2 > 0) 2252 AddLocalMinPoly(e1, e2, pt); 2253 break; 2254 } 2255 case ClipType.ctUnion: 2256 { 2257 if (e1Wc2 <= 0 && e2Wc2 <= 0) 2258 AddLocalMinPoly(e1, e2, pt); 2259 break; 2260 } 2261 case ClipType.ctDifference: 2262 { 2263 if (((e1.polyType == PolyType.ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || 2264 ((e1.polyType == PolyType.ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) 2265 AddLocalMinPoly(e1, e2, pt); 2266 break; 2267 } 2268 case ClipType.ctXor: 2269 { 2270 AddLocalMinPoly(e1, e2, pt); 2271 break; 2272 } 2273 } 2274 else 2275 SwapSides(e1, e2); 2276 } 2277 2278 if ((e1stops != e2stops) && 2279 ((e1stops && (e1.outIdx >= 0)) || (e2stops && (e2.outIdx >= 0)))) 2280 { 2281 SwapSides(e1, e2); 2282 SwapPolyIndexes(e1, e2); 2283 } 2284 2285 //finally, delete any non-contributing maxima edges ... 2286 if (e1stops) DeleteFromAEL(e1); 2287 if (e2stops) DeleteFromAEL(e2); 2288 } 2289 //------------------------------------------------------------------------------ 2290 DeleteFromAEL(TEdge e)2291 private void DeleteFromAEL(TEdge e) 2292 { 2293 TEdge AelPrev = e.prevInAEL; 2294 TEdge AelNext = e.nextInAEL; 2295 if (AelPrev == null && AelNext == null && (e != m_ActiveEdges)) 2296 return; //already deleted 2297 if (AelPrev != null) 2298 AelPrev.nextInAEL = AelNext; 2299 else m_ActiveEdges = AelNext; 2300 if (AelNext != null) 2301 AelNext.prevInAEL = AelPrev; 2302 e.nextInAEL = null; 2303 e.prevInAEL = null; 2304 } 2305 //------------------------------------------------------------------------------ 2306 DeleteFromSEL(TEdge e)2307 private void DeleteFromSEL(TEdge e) 2308 { 2309 TEdge SelPrev = e.prevInSEL; 2310 TEdge SelNext = e.nextInSEL; 2311 if (SelPrev == null && SelNext == null && (e != m_SortedEdges)) 2312 return; //already deleted 2313 if (SelPrev != null) 2314 SelPrev.nextInSEL = SelNext; 2315 else m_SortedEdges = SelNext; 2316 if (SelNext != null) 2317 SelNext.prevInSEL = SelPrev; 2318 e.nextInSEL = null; 2319 e.prevInSEL = null; 2320 } 2321 //------------------------------------------------------------------------------ 2322 UpdateEdgeIntoAEL(ref TEdge e)2323 private void UpdateEdgeIntoAEL(ref TEdge e) 2324 { 2325 if (e.nextInLML == null) 2326 throw new ClipperException("UpdateEdgeIntoAEL: invalid call"); 2327 TEdge AelPrev = e.prevInAEL; 2328 TEdge AelNext = e.nextInAEL; 2329 e.nextInLML.outIdx = e.outIdx; 2330 if (AelPrev != null) 2331 AelPrev.nextInAEL = e.nextInLML; 2332 else m_ActiveEdges = e.nextInLML; 2333 if (AelNext != null) 2334 AelNext.prevInAEL = e.nextInLML; 2335 e.nextInLML.side = e.side; 2336 e.nextInLML.windDelta = e.windDelta; 2337 e.nextInLML.windCnt = e.windCnt; 2338 e.nextInLML.windCnt2 = e.windCnt2; 2339 e = e.nextInLML; 2340 e.prevInAEL = AelPrev; 2341 e.nextInAEL = AelNext; 2342 if (e.dx != horizontal) InsertScanbeam(e.ytop); 2343 } 2344 //------------------------------------------------------------------------------ 2345 ProcessHorizontals()2346 private void ProcessHorizontals() 2347 { 2348 TEdge horzEdge = m_SortedEdges; 2349 while (horzEdge != null) 2350 { 2351 DeleteFromSEL(horzEdge); 2352 ProcessHorizontal(horzEdge); 2353 horzEdge = m_SortedEdges; 2354 } 2355 } 2356 //------------------------------------------------------------------------------ 2357 ProcessHorizontal(TEdge horzEdge)2358 private void ProcessHorizontal(TEdge horzEdge) 2359 { 2360 Direction Direction; 2361 Int64 horzLeft, horzRight; 2362 2363 if (horzEdge.xcurr < horzEdge.xtop) 2364 { 2365 horzLeft = horzEdge.xcurr; 2366 horzRight = horzEdge.xtop; 2367 Direction = Direction.dLeftToRight; 2368 } 2369 else 2370 { 2371 horzLeft = horzEdge.xtop; 2372 horzRight = horzEdge.xcurr; 2373 Direction = Direction.dRightToLeft; 2374 } 2375 2376 TEdge eMaxPair; 2377 if (horzEdge.nextInLML != null) 2378 eMaxPair = null; 2379 else 2380 eMaxPair = GetMaximaPair(horzEdge); 2381 2382 TEdge e = GetNextInAEL(horzEdge, Direction); 2383 while (e != null) 2384 { 2385 TEdge eNext = GetNextInAEL(e, Direction); 2386 if (eMaxPair != null || 2387 ((Direction == Direction.dLeftToRight) && (e.xcurr <= horzRight)) || 2388 ((Direction == Direction.dRightToLeft) && (e.xcurr >= horzLeft))) 2389 { 2390 //ok, so far it looks like we're still in range of the horizontal edge 2391 if (e.xcurr == horzEdge.xtop && eMaxPair == null) 2392 { 2393 if (SlopesEqual(e, horzEdge.nextInLML, m_UseFullRange)) 2394 { 2395 //if output polygons share an edge, they'll need joining later ... 2396 if (horzEdge.outIdx >= 0 && e.outIdx >= 0) 2397 AddJoin(horzEdge.nextInLML, e, horzEdge.outIdx, -1); 2398 break; //we've reached the end of the horizontal line 2399 } 2400 else if (e.dx < horzEdge.nextInLML.dx) 2401 //we really have got to the end of the intermediate horz edge so quit. 2402 //nb: More -ve slopes follow more +ve slopes ABOVE the horizontal. 2403 break; 2404 } 2405 2406 if (e == eMaxPair) 2407 { 2408 //horzEdge is evidently a maxima horizontal and we've arrived at its end. 2409 if (Direction == Direction.dLeftToRight) 2410 IntersectEdges(horzEdge, e, new IntPoint(e.xcurr, horzEdge.ycurr), 0); 2411 else 2412 IntersectEdges(e, horzEdge, new IntPoint(e.xcurr, horzEdge.ycurr), 0); 2413 if (eMaxPair.outIdx >= 0) throw new ClipperException("ProcessHorizontal error"); 2414 return; 2415 } 2416 else if (e.dx == horizontal && !IsMinima(e) && !(e.xcurr > e.xtop)) 2417 { 2418 if (Direction == Direction.dLeftToRight) 2419 IntersectEdges(horzEdge, e, new IntPoint(e.xcurr, horzEdge.ycurr), 2420 (IsTopHorz(horzEdge, e.xcurr)) ? Protects.ipLeft : Protects.ipBoth); 2421 else 2422 IntersectEdges(e, horzEdge, new IntPoint(e.xcurr, horzEdge.ycurr), 2423 (IsTopHorz(horzEdge, e.xcurr)) ? Protects.ipRight : Protects.ipBoth); 2424 } 2425 else if (Direction == Direction.dLeftToRight) 2426 { 2427 IntersectEdges(horzEdge, e, new IntPoint(e.xcurr, horzEdge.ycurr), 2428 (IsTopHorz(horzEdge, e.xcurr)) ? Protects.ipLeft : Protects.ipBoth); 2429 } 2430 else 2431 { 2432 IntersectEdges(e, horzEdge, new IntPoint(e.xcurr, horzEdge.ycurr), 2433 (IsTopHorz(horzEdge, e.xcurr)) ? Protects.ipRight : Protects.ipBoth); 2434 } 2435 SwapPositionsInAEL(horzEdge, e); 2436 } 2437 else if ( (Direction == Direction.dLeftToRight && 2438 e.xcurr > horzRight && horzEdge.nextInSEL == null) || 2439 (Direction == Direction.dRightToLeft && 2440 e.xcurr < horzLeft && horzEdge.nextInSEL == null) ) break; 2441 e = eNext; 2442 } //end while ( e ) 2443 2444 if (horzEdge.nextInLML != null) 2445 { 2446 if (horzEdge.outIdx >= 0) 2447 AddOutPt(horzEdge, new IntPoint(horzEdge.xtop, horzEdge.ytop)); 2448 UpdateEdgeIntoAEL(ref horzEdge); 2449 } 2450 else 2451 { 2452 if (horzEdge.outIdx >= 0) 2453 IntersectEdges(horzEdge, eMaxPair, 2454 new IntPoint(horzEdge.xtop, horzEdge.ycurr), Protects.ipBoth); 2455 DeleteFromAEL(eMaxPair); 2456 DeleteFromAEL(horzEdge); 2457 } 2458 } 2459 //------------------------------------------------------------------------------ 2460 IsTopHorz(TEdge horzEdge, double XPos)2461 private bool IsTopHorz(TEdge horzEdge, double XPos) 2462 { 2463 TEdge e = m_SortedEdges; 2464 while (e != null) 2465 { 2466 if ((XPos >= Math.Min(e.xcurr, e.xtop)) && (XPos <= Math.Max(e.xcurr, e.xtop))) 2467 return false; 2468 e = e.nextInSEL; 2469 } 2470 return true; 2471 } 2472 //------------------------------------------------------------------------------ 2473 GetNextInAEL(TEdge e, Direction Direction)2474 private TEdge GetNextInAEL(TEdge e, Direction Direction) 2475 { 2476 return Direction == Direction.dLeftToRight ? e.nextInAEL: e.prevInAEL; 2477 } 2478 //------------------------------------------------------------------------------ 2479 IsMinima(TEdge e)2480 private bool IsMinima(TEdge e) 2481 { 2482 return e != null && (e.prev.nextInLML != e) && (e.next.nextInLML != e); 2483 } 2484 //------------------------------------------------------------------------------ 2485 IsMaxima(TEdge e, double Y)2486 private bool IsMaxima(TEdge e, double Y) 2487 { 2488 return (e != null && e.ytop == Y && e.nextInLML == null); 2489 } 2490 //------------------------------------------------------------------------------ 2491 IsIntermediate(TEdge e, double Y)2492 private bool IsIntermediate(TEdge e, double Y) 2493 { 2494 return (e.ytop == Y && e.nextInLML != null); 2495 } 2496 //------------------------------------------------------------------------------ 2497 GetMaximaPair(TEdge e)2498 private TEdge GetMaximaPair(TEdge e) 2499 { 2500 if (!IsMaxima(e.next, e.ytop) || (e.next.xtop != e.xtop)) 2501 return e.prev; else 2502 return e.next; 2503 } 2504 //------------------------------------------------------------------------------ 2505 ProcessIntersections(Int64 botY, Int64 topY)2506 private bool ProcessIntersections(Int64 botY, Int64 topY) 2507 { 2508 if( m_ActiveEdges == null ) return true; 2509 try { 2510 BuildIntersectList(botY, topY); 2511 if ( m_IntersectNodes == null) return true; 2512 if ( FixupIntersections() ) ProcessIntersectList(); 2513 else return false; 2514 } 2515 catch { 2516 m_SortedEdges = null; 2517 DisposeIntersectNodes(); 2518 throw new ClipperException("ProcessIntersections error"); 2519 } 2520 return true; 2521 } 2522 //------------------------------------------------------------------------------ 2523 BuildIntersectList(Int64 botY, Int64 topY)2524 private void BuildIntersectList(Int64 botY, Int64 topY) 2525 { 2526 if ( m_ActiveEdges == null ) return; 2527 2528 //prepare for sorting ... 2529 TEdge e = m_ActiveEdges; 2530 e.tmpX = TopX( e, topY ); 2531 m_SortedEdges = e; 2532 m_SortedEdges.prevInSEL = null; 2533 e = e.nextInAEL; 2534 while( e != null ) 2535 { 2536 e.prevInSEL = e.prevInAEL; 2537 e.prevInSEL.nextInSEL = e; 2538 e.nextInSEL = null; 2539 e.tmpX = TopX( e, topY ); 2540 e = e.nextInAEL; 2541 } 2542 2543 //bubblesort ... 2544 bool isModified = true; 2545 while( isModified && m_SortedEdges != null ) 2546 { 2547 isModified = false; 2548 e = m_SortedEdges; 2549 while( e.nextInSEL != null ) 2550 { 2551 TEdge eNext = e.nextInSEL; 2552 IntPoint pt = new IntPoint(); 2553 if(e.tmpX > eNext.tmpX && IntersectPoint(e, eNext, ref pt)) 2554 { 2555 if (pt.Y > botY) 2556 { 2557 pt.Y = botY; 2558 pt.X = TopX(e, pt.Y); 2559 } 2560 AddIntersectNode(e, eNext, pt); 2561 SwapPositionsInSEL(e, eNext); 2562 isModified = true; 2563 } 2564 else 2565 e = eNext; 2566 } 2567 if( e.prevInSEL != null ) e.prevInSEL.nextInSEL = null; 2568 else break; 2569 } 2570 m_SortedEdges = null; 2571 } 2572 //------------------------------------------------------------------------------ 2573 FixupIntersections()2574 private bool FixupIntersections() 2575 { 2576 if ( m_IntersectNodes.next == null ) return true; 2577 2578 CopyAELToSEL(); 2579 IntersectNode int1 = m_IntersectNodes; 2580 IntersectNode int2 = m_IntersectNodes.next; 2581 while (int2 != null) 2582 { 2583 TEdge e1 = int1.edge1; 2584 TEdge e2; 2585 if (e1.prevInSEL == int1.edge2) e2 = e1.prevInSEL; 2586 else if (e1.nextInSEL == int1.edge2) e2 = e1.nextInSEL; 2587 else 2588 { 2589 //The current intersection is out of order, so try and swap it with 2590 //a subsequent intersection ... 2591 while (int2 != null) 2592 { 2593 if (int2.edge1.nextInSEL == int2.edge2 || 2594 int2.edge1.prevInSEL == int2.edge2) break; 2595 else int2 = int2.next; 2596 } 2597 if (int2 == null) return false; //oops!!! 2598 2599 //found an intersect node that can be swapped ... 2600 SwapIntersectNodes(int1, int2); 2601 e1 = int1.edge1; 2602 e2 = int1.edge2; 2603 } 2604 SwapPositionsInSEL(e1, e2); 2605 int1 = int1.next; 2606 int2 = int1.next; 2607 } 2608 2609 m_SortedEdges = null; 2610 2611 //finally, check the last intersection too ... 2612 return (int1.edge1.prevInSEL == int1.edge2 || int1.edge1.nextInSEL == int1.edge2); 2613 } 2614 //------------------------------------------------------------------------------ 2615 ProcessIntersectList()2616 private void ProcessIntersectList() 2617 { 2618 while( m_IntersectNodes != null ) 2619 { 2620 IntersectNode iNode = m_IntersectNodes.next; 2621 { 2622 IntersectEdges( m_IntersectNodes.edge1 , 2623 m_IntersectNodes.edge2 , m_IntersectNodes.pt, Protects.ipBoth ); 2624 SwapPositionsInAEL( m_IntersectNodes.edge1 , m_IntersectNodes.edge2 ); 2625 } 2626 m_IntersectNodes = null; 2627 m_IntersectNodes = iNode; 2628 } 2629 } 2630 //------------------------------------------------------------------------------ 2631 Round(double value)2632 private static Int64 Round(double value) 2633 { 2634 return value < 0 ? (Int64)(value - 0.5) : (Int64)(value + 0.5); 2635 } 2636 //------------------------------------------------------------------------------ 2637 TopX(TEdge edge, Int64 currentY)2638 private static Int64 TopX(TEdge edge, Int64 currentY) 2639 { 2640 if (currentY == edge.ytop) 2641 return edge.xtop; 2642 return edge.xbot + Round(edge.dx *(currentY - edge.ybot)); 2643 } 2644 //------------------------------------------------------------------------------ 2645 TopX(IntPoint pt1, IntPoint pt2, Int64 currentY)2646 private Int64 TopX(IntPoint pt1, IntPoint pt2, Int64 currentY) 2647 { 2648 //preconditions: pt1.Y <> pt2.Y and pt1.Y > pt2.Y 2649 if (currentY >= pt1.Y) return pt1.X; 2650 else if (currentY == pt2.Y) return pt2.X; 2651 else if (pt1.X == pt2.X) return pt1.X; 2652 else 2653 { 2654 double q = (pt1.X-pt2.X)/(pt1.Y-pt2.Y); 2655 return (Int64)Round(pt1.X + (currentY - pt1.Y) * q); 2656 } 2657 } 2658 //------------------------------------------------------------------------------ 2659 AddIntersectNode(TEdge e1, TEdge e2, IntPoint pt)2660 private void AddIntersectNode(TEdge e1, TEdge e2, IntPoint pt) 2661 { 2662 IntersectNode newNode = new IntersectNode(); 2663 newNode.edge1 = e1; 2664 newNode.edge2 = e2; 2665 newNode.pt = pt; 2666 newNode.next = null; 2667 if (m_IntersectNodes == null) m_IntersectNodes = newNode; 2668 else if( Process1Before2(newNode, m_IntersectNodes) ) 2669 { 2670 newNode.next = m_IntersectNodes; 2671 m_IntersectNodes = newNode; 2672 } 2673 else 2674 { 2675 IntersectNode iNode = m_IntersectNodes; 2676 while( iNode.next != null && Process1Before2(iNode.next, newNode) ) 2677 iNode = iNode.next; 2678 newNode.next = iNode.next; 2679 iNode.next = newNode; 2680 } 2681 } 2682 //------------------------------------------------------------------------------ 2683 Process1Before2(IntersectNode node1, IntersectNode node2)2684 private bool Process1Before2(IntersectNode node1, IntersectNode node2) 2685 { 2686 bool result; 2687 if (node1.pt.Y == node2.pt.Y) 2688 { 2689 if (node1.edge1 == node2.edge1 || node1.edge2 == node2.edge1) 2690 { 2691 result = node2.pt.X > node1.pt.X; 2692 return node2.edge1.dx > 0 ? !result : result; 2693 } 2694 else if (node1.edge1 == node2.edge2 || node1.edge2 == node2.edge2) 2695 { 2696 result = node2.pt.X > node1.pt.X; 2697 return node2.edge2.dx > 0 ? !result : result; 2698 } 2699 else return node2.pt.X > node1.pt.X; 2700 } 2701 else return node1.pt.Y > node2.pt.Y; 2702 } 2703 //------------------------------------------------------------------------------ 2704 SwapIntersectNodes(IntersectNode int1, IntersectNode int2)2705 private void SwapIntersectNodes(IntersectNode int1, IntersectNode int2) 2706 { 2707 TEdge e1 = int1.edge1; 2708 TEdge e2 = int1.edge2; 2709 IntPoint p = int1.pt; 2710 int1.edge1 = int2.edge1; 2711 int1.edge2 = int2.edge2; 2712 int1.pt = int2.pt; 2713 int2.edge1 = e1; 2714 int2.edge2 = e2; 2715 int2.pt = p; 2716 } 2717 //------------------------------------------------------------------------------ 2718 IntersectPoint(TEdge edge1, TEdge edge2, ref IntPoint ip)2719 private bool IntersectPoint(TEdge edge1, TEdge edge2, ref IntPoint ip) 2720 { 2721 double b1, b2; 2722 if (SlopesEqual(edge1, edge2, m_UseFullRange)) return false; 2723 else if (edge1.dx == 0) 2724 { 2725 ip.X = edge1.xbot; 2726 if (edge2.dx == horizontal) 2727 { 2728 ip.Y = edge2.ybot; 2729 } else 2730 { 2731 b2 = edge2.ybot - (edge2.xbot/edge2.dx); 2732 ip.Y = Round(ip.X/edge2.dx + b2); 2733 } 2734 } 2735 else if (edge2.dx == 0) 2736 { 2737 ip.X = edge2.xbot; 2738 if (edge1.dx == horizontal) 2739 { 2740 ip.Y = edge1.ybot; 2741 } else 2742 { 2743 b1 = edge1.ybot - (edge1.xbot/edge1.dx); 2744 ip.Y = Round(ip.X/edge1.dx + b1); 2745 } 2746 } else 2747 { 2748 b1 = edge1.xbot - edge1.ybot * edge1.dx; 2749 b2 = edge2.xbot - edge2.ybot * edge2.dx; 2750 b2 = (b2-b1)/(edge1.dx - edge2.dx); 2751 ip.Y = Round(b2); 2752 ip.X = Round(edge1.dx * b2 + b1); 2753 } 2754 2755 return 2756 //can be *so close* to the top of one edge that the rounded Y equals one ytop ... 2757 (ip.Y == edge1.ytop && ip.Y >= edge2.ytop && edge1.tmpX > edge2.tmpX) || 2758 (ip.Y == edge2.ytop && ip.Y >= edge1.ytop && edge1.tmpX > edge2.tmpX) || 2759 (ip.Y > edge1.ytop && ip.Y > edge2.ytop); 2760 } 2761 //------------------------------------------------------------------------------ 2762 DisposeIntersectNodes()2763 private void DisposeIntersectNodes() 2764 { 2765 while ( m_IntersectNodes != null ) 2766 { 2767 IntersectNode iNode = m_IntersectNodes.next; 2768 m_IntersectNodes = null; 2769 m_IntersectNodes = iNode; 2770 } 2771 } 2772 //------------------------------------------------------------------------------ 2773 ProcessEdgesAtTopOfScanbeam(Int64 topY)2774 private void ProcessEdgesAtTopOfScanbeam(Int64 topY) 2775 { 2776 TEdge e = m_ActiveEdges; 2777 while( e != null ) 2778 { 2779 //1. process maxima, treating them as if they're 'bent' horizontal edges, 2780 // but exclude maxima with horizontal edges. nb: e can't be a horizontal. 2781 if( IsMaxima(e, topY) && GetMaximaPair(e).dx != horizontal ) 2782 { 2783 //'e' might be removed from AEL, as may any following edges so ... 2784 TEdge ePrior = e.prevInAEL; 2785 DoMaxima(e, topY); 2786 if( ePrior == null ) e = m_ActiveEdges; 2787 else e = ePrior.nextInAEL; 2788 } 2789 else 2790 { 2791 //2. promote horizontal edges, otherwise update xcurr and ycurr ... 2792 if( IsIntermediate(e, topY) && e.nextInLML.dx == horizontal ) 2793 { 2794 if (e.outIdx >= 0) 2795 { 2796 AddOutPt(e, new IntPoint(e.xtop, e.ytop)); 2797 2798 for (int i = 0; i < m_HorizJoins.Count; ++i) 2799 { 2800 IntPoint pt = new IntPoint(), pt2 = new IntPoint(); 2801 HorzJoinRec hj = m_HorizJoins[i]; 2802 if (GetOverlapSegment(new IntPoint(hj.edge.xbot, hj.edge.ybot), 2803 new IntPoint(hj.edge.xtop, hj.edge.ytop), 2804 new IntPoint(e.nextInLML.xbot, e.nextInLML.ybot), 2805 new IntPoint(e.nextInLML.xtop, e.nextInLML.ytop), ref pt, ref pt2)) 2806 AddJoin(hj.edge, e.nextInLML, hj.savedIdx, e.outIdx); 2807 } 2808 2809 AddHorzJoin(e.nextInLML, e.outIdx); 2810 } 2811 UpdateEdgeIntoAEL(ref e); 2812 AddEdgeToSEL(e); 2813 } 2814 else 2815 { 2816 //this just simplifies horizontal processing ... 2817 e.xcurr = TopX( e, topY ); 2818 e.ycurr = topY; 2819 } 2820 e = e.nextInAEL; 2821 } 2822 } 2823 2824 //3. Process horizontals at the top of the scanbeam ... 2825 ProcessHorizontals(); 2826 2827 //4. Promote intermediate vertices ... 2828 e = m_ActiveEdges; 2829 while( e != null ) 2830 { 2831 if( IsIntermediate( e, topY ) ) 2832 { 2833 if (e.outIdx >= 0) AddOutPt(e, new IntPoint(e.xtop, e.ytop)); 2834 UpdateEdgeIntoAEL(ref e); 2835 2836 //if output polygons share an edge, they'll need joining later ... 2837 if (e.outIdx >= 0 && e.prevInAEL != null && e.prevInAEL.outIdx >= 0 && 2838 e.prevInAEL.xcurr == e.xbot && e.prevInAEL.ycurr == e.ybot && 2839 SlopesEqual(new IntPoint(e.xbot, e.ybot), new IntPoint(e.xtop, e.ytop), 2840 new IntPoint(e.xbot, e.ybot), 2841 new IntPoint(e.prevInAEL.xtop, e.prevInAEL.ytop), m_UseFullRange)) 2842 { 2843 AddOutPt(e.prevInAEL, new IntPoint(e.xbot, e.ybot)); 2844 AddJoin(e, e.prevInAEL, -1, -1); 2845 } 2846 else if (e.outIdx >= 0 && e.nextInAEL != null && e.nextInAEL.outIdx >= 0 && 2847 e.nextInAEL.ycurr > e.nextInAEL.ytop && 2848 e.nextInAEL.ycurr <= e.nextInAEL.ybot && 2849 e.nextInAEL.xcurr == e.xbot && e.nextInAEL.ycurr == e.ybot && 2850 SlopesEqual(new IntPoint(e.xbot, e.ybot), new IntPoint(e.xtop, e.ytop), 2851 new IntPoint(e.xbot, e.ybot), 2852 new IntPoint(e.nextInAEL.xtop, e.nextInAEL.ytop), m_UseFullRange)) 2853 { 2854 AddOutPt(e.nextInAEL, new IntPoint(e.xbot, e.ybot)); 2855 AddJoin(e, e.nextInAEL, -1, -1); 2856 } 2857 2858 } 2859 e = e.nextInAEL; 2860 } 2861 } 2862 //------------------------------------------------------------------------------ 2863 DoMaxima(TEdge e, Int64 topY)2864 private void DoMaxima(TEdge e, Int64 topY) 2865 { 2866 TEdge eMaxPair = GetMaximaPair(e); 2867 Int64 X = e.xtop; 2868 TEdge eNext = e.nextInAEL; 2869 while( eNext != eMaxPair ) 2870 { 2871 if (eNext == null) throw new ClipperException("DoMaxima error"); 2872 IntersectEdges( e, eNext, new IntPoint(X, topY), Protects.ipBoth ); 2873 eNext = eNext.nextInAEL; 2874 } 2875 if( e.outIdx < 0 && eMaxPair.outIdx < 0 ) 2876 { 2877 DeleteFromAEL( e ); 2878 DeleteFromAEL( eMaxPair ); 2879 } 2880 else if( e.outIdx >= 0 && eMaxPair.outIdx >= 0 ) 2881 { 2882 IntersectEdges(e, eMaxPair, new IntPoint(X, topY), Protects.ipNone); 2883 } 2884 else throw new ClipperException("DoMaxima error"); 2885 } 2886 //------------------------------------------------------------------------------ 2887 ReversePolygons(Polygons polys)2888 public static void ReversePolygons(Polygons polys) 2889 { 2890 foreach (var poly in polys) poly.Reverse(); 2891 } 2892 //------------------------------------------------------------------------------ 2893 Orientation(Polygon poly)2894 public static bool Orientation(Polygon poly) 2895 { 2896 int highI = poly.Count -1; 2897 if (highI < 2) return false; 2898 int j = 0, jplus, jminus; 2899 for (int i = 0; i <= highI; ++i) 2900 { 2901 if (poly[i].Y < poly[j].Y) continue; 2902 if ((poly[i].Y > poly[j].Y || poly[i].X < poly[j].X)) j = i; 2903 }; 2904 if (j == highI) jplus = 0; 2905 else jplus = j +1; 2906 if (j == 0) jminus = highI; 2907 else jminus = j -1; 2908 2909 //get cross product of vectors of the edges adjacent to highest point ... 2910 IntPoint vec1 = new IntPoint(poly[j].X - poly[jminus].X, poly[j].Y - poly[jminus].Y); 2911 IntPoint vec2 = new IntPoint(poly[jplus].X - poly[j].X, poly[jplus].Y - poly[j].Y); 2912 if (Math.Abs(vec1.X) > loRange || Math.Abs(vec1.Y) > loRange || 2913 Math.Abs(vec2.X) > loRange || Math.Abs(vec2.Y) > loRange) 2914 { 2915 if (Math.Abs(vec1.X) > hiRange || Math.Abs(vec1.Y) > hiRange || 2916 Math.Abs(vec2.X) > hiRange || Math.Abs(vec2.Y) > hiRange) 2917 throw new ClipperException("Coordinate exceeds range bounds."); 2918 Int128 cross = Int128.Int128Mul(vec1.X, vec2.Y) - Int128.Int128Mul(vec2.X, vec1.Y); 2919 return !cross.IsNegative(); 2920 } 2921 else 2922 return (vec1.X * vec2.Y - vec2.X * vec1.Y) > 0; 2923 } 2924 //------------------------------------------------------------------------------ 2925 FullRangeNeeded(Polygon pts)2926 private static bool FullRangeNeeded(Polygon pts) 2927 { 2928 bool result = false; 2929 for (int i = 0; i < pts.Count; i++) 2930 { 2931 if (Math.Abs(pts[i].X) > hiRange || Math.Abs(pts[i].Y) > hiRange) 2932 throw new ClipperException("Coordinate exceeds range bounds."); 2933 else if (Math.Abs(pts[i].X) > loRange || Math.Abs(pts[i].Y) > loRange) 2934 result = true; 2935 } 2936 return result; 2937 } 2938 //------------------------------------------------------------------------------ 2939 Orientation(OutRec outRec, bool UseFull64BitRange)2940 private bool Orientation(OutRec outRec, bool UseFull64BitRange) 2941 { 2942 //first make sure bottomPt is correctly assigned ... 2943 OutPt opBottom = outRec.pts, op = outRec.pts.next; 2944 while (op != outRec.pts) 2945 { 2946 if (op.pt.Y >= opBottom.pt.Y) 2947 { 2948 if (op.pt.Y > opBottom.pt.Y || op.pt.X < opBottom.pt.X) 2949 opBottom = op; 2950 } 2951 op = op.next; 2952 } 2953 outRec.bottomPt = opBottom; 2954 opBottom.idx = outRec.idx; 2955 2956 op = opBottom; 2957 //find vertices either side of bottomPt (skipping duplicate points) .... 2958 OutPt opPrev = op.prev; 2959 OutPt opNext = op.next; 2960 while (op != opPrev && PointsEqual(op.pt, opPrev.pt)) 2961 opPrev = opPrev.prev; 2962 while (op != opNext && PointsEqual(op.pt, opNext.pt)) 2963 opNext = opNext.next; 2964 2965 IntPoint vec1 = new IntPoint(op.pt.X - opPrev.pt.X, op.pt.Y - opPrev.pt.Y); 2966 IntPoint vec2 = new IntPoint(opNext.pt.X - op.pt.X, opNext.pt.Y - op.pt.Y); 2967 2968 if (UseFull64BitRange) 2969 { 2970 Int128 cross = Int128.Int128Mul(vec1.X, vec2.Y) - Int128.Int128Mul(vec2.X, vec1.Y); 2971 return !cross.IsNegative(); 2972 } 2973 else 2974 return (vec1.X * vec2.Y - vec2.X * vec1.Y) > 0; 2975 2976 } 2977 //------------------------------------------------------------------------------ 2978 PointCount(OutPt pts)2979 private int PointCount(OutPt pts) 2980 { 2981 if (pts == null) return 0; 2982 int result = 0; 2983 OutPt p = pts; 2984 do 2985 { 2986 result++; 2987 p = p.next; 2988 } 2989 while (p != pts); 2990 return result; 2991 } 2992 //------------------------------------------------------------------------------ 2993 BuildResult(Polygons polyg)2994 private void BuildResult(Polygons polyg) 2995 { 2996 polyg.Clear(); 2997 polyg.Capacity = m_PolyOuts.Count; 2998 foreach (OutRec outRec in m_PolyOuts) 2999 { 3000 if (outRec.pts == null) continue; 3001 OutPt p = outRec.pts; 3002 int cnt = PointCount(p); 3003 if (cnt < 3) continue; 3004 Polygon pg = new Polygon(cnt); 3005 for (int j = 0; j < cnt; j++) 3006 { 3007 pg.Add(p.pt); 3008 p = p.next; 3009 } 3010 polyg.Add(pg); 3011 } 3012 } 3013 //------------------------------------------------------------------------------ 3014 BuildResultEx(ExPolygons polyg)3015 private void BuildResultEx(ExPolygons polyg) 3016 { 3017 polyg.Clear(); 3018 polyg.Capacity = m_PolyOuts.Count; 3019 int i = 0; 3020 while (i < m_PolyOuts.Count) 3021 { 3022 OutRec outRec = m_PolyOuts[i++]; 3023 if (outRec.pts == null) break; //nb: already sorted here 3024 OutPt p = outRec.pts; 3025 int cnt = PointCount(p); 3026 if (cnt < 3) continue; 3027 ExPolygon epg = new ExPolygon(); 3028 epg.outer = new Polygon(cnt); 3029 epg.holes = new Polygons(); 3030 for (int j = 0; j < cnt; j++) 3031 { 3032 epg.outer.Add(p.pt); 3033 p = p.next; 3034 } 3035 while (i < m_PolyOuts.Count) 3036 { 3037 outRec = m_PolyOuts[i]; 3038 if (outRec.pts == null || !outRec.isHole) break; 3039 Polygon pg = new Polygon(); 3040 p = outRec.pts; 3041 do 3042 { 3043 pg.Add(p.pt); 3044 p = p.next; 3045 } while (p != outRec.pts); 3046 epg.holes.Add(pg); 3047 i++; 3048 } 3049 polyg.Add(epg); 3050 } 3051 } 3052 //------------------------------------------------------------------------------ 3053 FixupOutPolygon(OutRec outRec)3054 private void FixupOutPolygon(OutRec outRec) 3055 { 3056 //FixupOutPolygon() - removes duplicate points and simplifies consecutive 3057 //parallel edges by removing the middle vertex. 3058 OutPt lastOK = null; 3059 outRec.pts = outRec.bottomPt; 3060 OutPt pp = outRec.bottomPt; 3061 for (;;) 3062 { 3063 if (pp.prev == pp || pp.prev == pp.next) 3064 { 3065 DisposeOutPts(pp); 3066 outRec.pts = null; 3067 outRec.bottomPt = null; 3068 return; 3069 } 3070 //test for duplicate points and for same slope (cross-product) ... 3071 if (PointsEqual(pp.pt, pp.next.pt) || 3072 SlopesEqual(pp.prev.pt, pp.pt, pp.next.pt, m_UseFullRange)) 3073 { 3074 lastOK = null; 3075 if (pp == outRec.bottomPt) 3076 outRec.bottomPt = null; //flags need for updating 3077 pp.prev.next = pp.next; 3078 pp.next.prev = pp.prev; 3079 pp = pp.prev; 3080 } 3081 else if (pp == lastOK) break; 3082 else 3083 { 3084 if (lastOK == null) lastOK = pp; 3085 pp = pp.next; 3086 } 3087 } 3088 if (outRec.bottomPt == null) 3089 { 3090 outRec.bottomPt = GetBottomPt(pp); 3091 outRec.bottomPt.idx = outRec.idx; 3092 outRec.pts = outRec.bottomPt; 3093 } 3094 } 3095 //------------------------------------------------------------------------------ 3096 CheckHoleLinkages1(OutRec outRec1, OutRec outRec2)3097 private void CheckHoleLinkages1(OutRec outRec1, OutRec outRec2) 3098 { 3099 //when a polygon is split into 2 polygons, make sure any holes the original 3100 //polygon contained link to the correct polygon ... 3101 for (int i = 0; i < m_PolyOuts.Count; ++i) 3102 { 3103 if (m_PolyOuts[i].isHole && m_PolyOuts[i].bottomPt != null && 3104 m_PolyOuts[i].FirstLeft == outRec1 && 3105 !PointInPolygon(m_PolyOuts[i].bottomPt.pt, 3106 outRec1.pts, m_UseFullRange)) 3107 m_PolyOuts[i].FirstLeft = outRec2; 3108 } 3109 } 3110 //---------------------------------------------------------------------- 3111 CheckHoleLinkages2(OutRec outRec1, OutRec outRec2)3112 private void CheckHoleLinkages2(OutRec outRec1, OutRec outRec2) 3113 { 3114 //if a hole is owned by outRec2 then make it owned by outRec1 ... 3115 for (int i = 0; i < m_PolyOuts.Count; ++i) 3116 if (m_PolyOuts[i].isHole && m_PolyOuts[i].bottomPt != null && 3117 m_PolyOuts[i].FirstLeft == outRec2) 3118 m_PolyOuts[i].FirstLeft = outRec1; 3119 } 3120 //---------------------------------------------------------------------- 3121 JoinCommonEdges(bool fixHoleLinkages)3122 private void JoinCommonEdges(bool fixHoleLinkages) 3123 { 3124 for (int i = 0; i < m_Joins.Count; i++) 3125 { 3126 JoinRec j = m_Joins[i]; 3127 OutRec outRec1 = m_PolyOuts[j.poly1Idx]; 3128 OutPt pp1a = outRec1.pts; 3129 OutRec outRec2 = m_PolyOuts[j.poly2Idx]; 3130 OutPt pp2a = outRec2.pts; 3131 IntPoint pt1 = new IntPoint(j.pt2a); 3132 IntPoint pt2 = new IntPoint(j.pt2b); 3133 IntPoint pt3 = new IntPoint(j.pt1a); 3134 IntPoint pt4 = new IntPoint(j.pt1b); 3135 if (!FindSegment(ref pp1a, ref pt1, ref pt2)) continue; 3136 if (j.poly1Idx == j.poly2Idx) 3137 { 3138 //we're searching the same polygon for overlapping segments so 3139 //segment 2 mustn't be the same as segment 1 ... 3140 pp2a = pp1a.next; 3141 if (!FindSegment(ref pp2a, ref pt3, ref pt4) || (pp2a == pp1a)) continue; 3142 } 3143 else if (!FindSegment(ref pp2a, ref pt3, ref pt4)) continue; 3144 3145 if (!GetOverlapSegment(pt1, pt2, pt3, pt4, ref pt1, ref pt2)) continue; 3146 3147 OutPt p1, p2, p3, p4; 3148 OutPt prev = pp1a.prev; 3149 //get p1 & p2 polypts - the overlap start & endpoints on poly1 3150 3151 if (PointsEqual(pp1a.pt, pt1)) p1 = pp1a; 3152 else if (PointsEqual(prev.pt, pt1)) p1 = prev; 3153 else p1 = InsertPolyPtBetween(pp1a, prev, pt1); 3154 3155 if (PointsEqual(pp1a.pt, pt2)) p2 = pp1a; 3156 else if (PointsEqual(prev.pt, pt2)) p2 = prev; 3157 else if ((p1 == pp1a) || (p1 == prev)) 3158 p2 = InsertPolyPtBetween(pp1a, prev, pt2); 3159 else if (Pt3IsBetweenPt1AndPt2(pp1a.pt, p1.pt, pt2)) 3160 p2 = InsertPolyPtBetween(pp1a, p1, pt2); 3161 else 3162 p2 = InsertPolyPtBetween(p1, prev, pt2); 3163 3164 //get p3 & p4 polypts - the overlap start & endpoints on poly2 3165 prev = pp2a.prev; 3166 if (PointsEqual(pp2a.pt, pt1)) p3 = pp2a; 3167 else if (PointsEqual(prev.pt, pt1)) p3 = prev; 3168 else p3 = InsertPolyPtBetween(pp2a, prev, pt1); 3169 3170 if (PointsEqual(pp2a.pt, pt2)) p4 = pp2a; 3171 else if (PointsEqual(prev.pt, pt2)) p4 = prev; 3172 else if ((p3 == pp2a) || (p3 == prev)) 3173 p4 = InsertPolyPtBetween(pp2a, prev, pt2); 3174 else if (Pt3IsBetweenPt1AndPt2(pp2a.pt, p3.pt, pt2)) 3175 p4 = InsertPolyPtBetween(pp2a, p3, pt2); 3176 else 3177 p4 = InsertPolyPtBetween(p3, prev, pt2); 3178 3179 //p1.pt should equal p3.pt and p2.pt should equal p4.pt here, so ... 3180 //join p1 to p3 and p2 to p4 ... 3181 if (p1.next == p2 && p3.prev == p4) 3182 { 3183 p1.next = p3; 3184 p3.prev = p1; 3185 p2.prev = p4; 3186 p4.next = p2; 3187 } 3188 else if (p1.prev == p2 && p3.next == p4) 3189 { 3190 p1.prev = p3; 3191 p3.next = p1; 3192 p2.next = p4; 3193 p4.prev = p2; 3194 } 3195 else 3196 continue; //an orientation is probably wrong 3197 3198 if (j.poly2Idx == j.poly1Idx) 3199 { 3200 //instead of joining two polygons, we've just created a new one by 3201 //splitting one polygon into two. 3202 outRec1.pts = GetBottomPt(p1); 3203 outRec1.bottomPt = outRec1.pts; 3204 outRec1.bottomPt.idx = outRec1.idx; 3205 outRec2 = CreateOutRec(); 3206 m_PolyOuts.Add(outRec2); 3207 outRec2.idx = m_PolyOuts.Count - 1; 3208 j.poly2Idx = outRec2.idx; 3209 outRec2.pts = GetBottomPt(p2); 3210 outRec2.bottomPt = outRec2.pts; 3211 outRec2.bottomPt.idx = outRec2.idx; 3212 3213 if (PointInPolygon(outRec2.pts.pt, outRec1.pts, m_UseFullRange)) 3214 { 3215 //outRec1 is contained by outRec2 ... 3216 outRec2.isHole = !outRec1.isHole; 3217 outRec2.FirstLeft = outRec1; 3218 if (outRec2.isHole == Orientation(outRec2, m_UseFullRange)) 3219 ReversePolyPtLinks(outRec2.pts); 3220 } 3221 else if (PointInPolygon(outRec1.pts.pt, outRec2.pts, m_UseFullRange)) 3222 { 3223 //outRec2 is contained by outRec1 ... 3224 outRec2.isHole = outRec1.isHole; 3225 outRec1.isHole = !outRec2.isHole; 3226 outRec2.FirstLeft = outRec1.FirstLeft; 3227 outRec1.FirstLeft = outRec2; 3228 if (outRec1.isHole == Orientation(outRec1, m_UseFullRange)) 3229 ReversePolyPtLinks(outRec1.pts); 3230 //make sure any contained holes now link to the correct polygon ... 3231 if (fixHoleLinkages) CheckHoleLinkages1(outRec1, outRec2); 3232 } 3233 else 3234 { 3235 outRec2.isHole = outRec1.isHole; 3236 outRec2.FirstLeft = outRec1.FirstLeft; 3237 //make sure any contained holes now link to the correct polygon ... 3238 if (fixHoleLinkages) CheckHoleLinkages1(outRec1, outRec2); 3239 } 3240 3241 //now fixup any subsequent m_Joins that match this polygon 3242 for (int k = i + 1; k < m_Joins.Count; k++) 3243 { 3244 JoinRec j2 = m_Joins[k]; 3245 if (j2.poly1Idx == j.poly1Idx && PointIsVertex(j2.pt1a, p2)) 3246 j2.poly1Idx = j.poly2Idx; 3247 if (j2.poly2Idx == j.poly1Idx && PointIsVertex(j2.pt2a, p2)) 3248 j2.poly2Idx = j.poly2Idx; 3249 } 3250 3251 //now cleanup redundant edges too ... 3252 FixupOutPolygon(outRec1); 3253 FixupOutPolygon(outRec2); 3254 } 3255 else 3256 { 3257 //joined 2 polygons together ... 3258 3259 //make sure any holes contained by outRec2 now link to outRec1 ... 3260 if (fixHoleLinkages) CheckHoleLinkages2(outRec1, outRec2); 3261 3262 //now cleanup redundant edges too ... 3263 FixupOutPolygon(outRec1); 3264 3265 if (outRec1.pts != null) 3266 { 3267 outRec1.isHole = !Orientation(outRec1, m_UseFullRange); 3268 if (outRec1.isHole && outRec1.FirstLeft == null) 3269 outRec1.FirstLeft = outRec2.FirstLeft; 3270 } 3271 3272 //delete the obsolete pointer ... 3273 int OKIdx = outRec1.idx; 3274 int ObsoleteIdx = outRec2.idx; 3275 outRec2.pts = null; 3276 outRec2.bottomPt = null; 3277 outRec2.AppendLink = outRec1; 3278 3279 //now fixup any subsequent joins that match this polygon 3280 for (int k = i + 1; k < m_Joins.Count; k++) 3281 { 3282 JoinRec j2 = m_Joins[k]; 3283 if (j2.poly1Idx == ObsoleteIdx) j2.poly1Idx = OKIdx; 3284 if (j2.poly2Idx == ObsoleteIdx) j2.poly2Idx = OKIdx; 3285 } 3286 } 3287 } 3288 } 3289 //------------------------------------------------------------------------------ 3290 Area(Polygon poly)3291 public static double Area(Polygon poly) 3292 { 3293 int highI = poly.Count - 1; 3294 if (highI < 2) return 0; 3295 if (FullRangeNeeded(poly)) 3296 { 3297 Int128 a = new Int128(); 3298 a = Int128.Int128Mul(poly[highI].X, poly[0].Y) - 3299 Int128.Int128Mul(poly[0].X, poly[highI].Y); 3300 for (int i = 0; i < highI; ++i) 3301 a += Int128.Int128Mul(poly[i].X, poly[i + 1].Y) - 3302 Int128.Int128Mul(poly[i + 1].X, poly[i].Y); 3303 return a.ToDouble() / 2; 3304 } 3305 else 3306 { 3307 double area = (double)poly[highI].X * (double)poly[0].Y - 3308 (double)poly[0].X * (double)poly[highI].Y; 3309 for (int i = 0; i < highI; ++i) 3310 area += (double)poly[i].X * (double)poly[i + 1].Y - 3311 (double)poly[i + 1].X * (double)poly[i].Y; 3312 return area / 2; 3313 } 3314 } 3315 //------------------------------------------------------------------------------ 3316 Area(OutRec outRec, bool UseFull64BitRange)3317 double Area(OutRec outRec, bool UseFull64BitRange) 3318 { 3319 OutPt op = outRec.pts; 3320 if (UseFull64BitRange) 3321 { 3322 Int128 a = new Int128(0); 3323 do 3324 { 3325 a += Int128.Int128Mul(op.prev.pt.X, op.pt.Y) - 3326 Int128.Int128Mul(op.pt.X, op.prev.pt.Y); 3327 op = op.next; 3328 } while (op != outRec.pts); 3329 return a.ToDouble() / 2; 3330 } 3331 else 3332 { 3333 double a = 0; 3334 do { 3335 a += (op.prev.pt.X * op.pt.Y) - (op.pt.X * op.prev.pt.Y); 3336 op = op.next; 3337 } while (op != outRec.pts); 3338 return a/2; 3339 } 3340 } 3341 3342 //------------------------------------------------------------------------------ 3343 // OffsetPolygon functions ... 3344 //------------------------------------------------------------------------------ 3345 BuildArc(IntPoint pt, double a1, double a2, double r)3346 internal static Polygon BuildArc(IntPoint pt, double a1, double a2, double r) 3347 { 3348 int steps = Math.Max(6, (int)(Math.Sqrt(Math.Abs(r)) * Math.Abs(a2 - a1))); 3349 Polygon result = new Polygon(steps); 3350 int n = steps - 1; 3351 double da = (a2 - a1) / n; 3352 double a = a1; 3353 for (int i = 0; i < steps; ++i) 3354 { 3355 result.Add(new IntPoint(pt.X + Round(Math.Cos(a) * r), pt.Y + Round(Math.Sin(a) * r))); 3356 a += da; 3357 } 3358 return result; 3359 } 3360 //------------------------------------------------------------------------------ 3361 GetUnitNormal(IntPoint pt1, IntPoint pt2)3362 internal static DoublePoint GetUnitNormal(IntPoint pt1, IntPoint pt2) 3363 { 3364 double dx = (pt2.X - pt1.X); 3365 double dy = (pt2.Y - pt1.Y); 3366 if ((dx == 0) && (dy == 0)) return new DoublePoint(); 3367 3368 double f = 1 * 1.0 / Math.Sqrt(dx * dx + dy * dy); 3369 dx *= f; 3370 dy *= f; 3371 3372 return new DoublePoint(dy, -dx); 3373 } 3374 //------------------------------------------------------------------------------ 3375 3376 internal class DoublePoint 3377 { 3378 public double X { get; set; } 3379 public double Y { get; set; } DoublePoint(double x = 0, double y = 0)3380 public DoublePoint(double x = 0, double y = 0) 3381 { 3382 this.X = x; this.Y = y; 3383 } 3384 }; 3385 //------------------------------------------------------------------------------ 3386 3387 private class PolyOffsetBuilder 3388 { 3389 private Polygons pts; 3390 private Polygon currentPoly; 3391 private List<DoublePoint> normals; 3392 private double delta, m_R; 3393 private int m_i, m_j, m_k; 3394 private const int buffLength = 128; 3395 PolyOffsetBuilder(Polygons pts, Polygons solution, double delta, JoinType jointype, double MiterLimit = 2)3396 public PolyOffsetBuilder(Polygons pts, Polygons solution, double delta, JoinType jointype, double MiterLimit = 2) 3397 { 3398 //precondtion: solution != pts 3399 3400 if (delta == 0) 3401 { 3402 solution = pts; 3403 return; 3404 } 3405 3406 this.pts = pts; 3407 this.delta = delta; 3408 if (MiterLimit <= 1) MiterLimit = 1; 3409 double RMin = 2/(MiterLimit*MiterLimit); 3410 3411 normals = new List<DoublePoint>(); 3412 3413 solution.Clear(); 3414 solution.Capacity = pts.Count; 3415 for (m_i = 0; m_i < pts.Count; m_i++) 3416 { 3417 int len = pts[m_i].Count; 3418 if (len > 1 && pts[m_i][0].X == pts[m_i][len - 1].X && 3419 pts[m_i][0].Y == pts[m_i][len - 1].Y) len--; 3420 3421 if (len == 0 || (len < 3 && delta <= 0)) 3422 continue; 3423 else if (len == 1) 3424 { 3425 Polygon arc; 3426 arc = BuildArc(pts[m_i][len - 1], 0, 2 * Math.PI, delta); 3427 solution.Add(arc); 3428 continue; 3429 } 3430 3431 //build normals ... 3432 normals.Clear(); 3433 normals.Capacity = len; 3434 for (int j = 0; j < len -1; ++j) 3435 normals.Add(GetUnitNormal(pts[m_i][j], pts[m_i][j+1])); 3436 normals.Add(GetUnitNormal(pts[m_i][len - 1], pts[m_i][0])); 3437 3438 currentPoly = new Polygon(); 3439 m_k = len - 1; 3440 for (m_j = 0; m_j < len; ++m_j) 3441 { 3442 switch (jointype) 3443 { 3444 case JoinType.jtMiter: 3445 { 3446 m_R = 1 + (normals[m_j].X*normals[m_k].X + 3447 normals[m_j].Y*normals[m_k].Y); 3448 if (m_R >= RMin) DoMiter(); else DoSquare(MiterLimit); 3449 break; 3450 } 3451 case JoinType.jtRound: 3452 DoRound(); 3453 break; 3454 case JoinType.jtSquare: 3455 DoSquare(1); 3456 break; 3457 } 3458 m_k = m_j; 3459 } 3460 solution.Add(currentPoly); 3461 } 3462 3463 //finally, clean up untidy corners ... 3464 Clipper clpr = new Clipper(); 3465 clpr.AddPolygons(solution, PolyType.ptSubject); 3466 if (delta > 0) 3467 { 3468 clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftPositive, PolyFillType.pftPositive); 3469 } 3470 else 3471 { 3472 IntRect r = clpr.GetBounds(); 3473 Polygon outer = new Polygon(4); 3474 3475 outer.Add(new IntPoint(r.left - 10, r.bottom + 10)); 3476 outer.Add(new IntPoint(r.right + 10, r.bottom + 10)); 3477 outer.Add(new IntPoint(r.right + 10, r.top - 10)); 3478 outer.Add(new IntPoint(r.left - 10, r.top - 10)); 3479 3480 clpr.AddPolygon(outer, PolyType.ptSubject); 3481 clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); 3482 if (solution.Count > 0) 3483 { 3484 solution.RemoveAt(0); 3485 for (int i = 0; i < solution.Count; i++) 3486 solution[i].Reverse(); 3487 } 3488 } 3489 } 3490 //------------------------------------------------------------------------------ 3491 AddPoint(IntPoint pt)3492 internal void AddPoint(IntPoint pt) 3493 { 3494 int len = currentPoly.Count; 3495 if (len == currentPoly.Capacity) 3496 currentPoly.Capacity = len + buffLength; 3497 currentPoly.Add(pt); 3498 } 3499 //------------------------------------------------------------------------------ 3500 DoSquare(double mul)3501 internal void DoSquare(double mul) 3502 { 3503 IntPoint pt1 = new IntPoint((Int64)Round(pts[m_i][m_j].X + normals[m_k].X * delta), 3504 (Int64)Round(pts[m_i][m_j].Y + normals[m_k].Y * delta)); 3505 IntPoint pt2 = new IntPoint((Int64)Round(pts[m_i][m_j].X + normals[m_j].X * delta), 3506 (Int64)Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); 3507 if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * delta >= 0) 3508 { 3509 double a1 = Math.Atan2(normals[m_k].Y, normals[m_k].X); 3510 double a2 = Math.Atan2(-normals[m_j].Y, -normals[m_j].X); 3511 a1 = Math.Abs(a2 - a1); 3512 if (a1 > Math.PI) a1 = Math.PI * 2 - a1; 3513 double dx = Math.Tan((Math.PI - a1) / 4) * Math.Abs(delta * mul); 3514 pt1 = new IntPoint((Int64)(pt1.X - normals[m_k].Y * dx), 3515 (Int64)(pt1.Y + normals[m_k].X * dx)); 3516 AddPoint(pt1); 3517 pt2 = new IntPoint((Int64)(pt2.X + normals[m_j].Y * dx), 3518 (Int64)(pt2.Y - normals[m_j].X * dx)); 3519 AddPoint(pt2); 3520 } 3521 else 3522 { 3523 AddPoint(pt1); 3524 AddPoint(pts[m_i][m_j]); 3525 AddPoint(pt2); 3526 } 3527 } 3528 //------------------------------------------------------------------------------ 3529 DoMiter()3530 internal void DoMiter() 3531 { 3532 if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * delta >= 0) 3533 { 3534 double q = delta / m_R; 3535 AddPoint(new IntPoint((Int64)Round(pts[m_i][m_j].X + 3536 (normals[m_k].X + normals[m_j].X) * q), 3537 (Int64)Round(pts[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q))); 3538 } 3539 else 3540 { 3541 IntPoint pt1 = new IntPoint((Int64)Round(pts[m_i][m_j].X + normals[m_k].X * delta), 3542 (Int64)Round(pts[m_i][m_j].Y + normals[m_k].Y * delta)); 3543 IntPoint pt2 = new IntPoint((Int64)Round(pts[m_i][m_j].X + normals[m_j].X * delta), 3544 (Int64)Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); 3545 AddPoint(pt1); 3546 AddPoint(pts[m_i][m_j]); 3547 AddPoint(pt2); 3548 } 3549 } 3550 //------------------------------------------------------------------------------ 3551 DoRound()3552 internal void DoRound() 3553 { 3554 IntPoint pt1 = new IntPoint(Round(pts[m_i][m_j].X + normals[m_k].X * delta), 3555 Round(pts[m_i][m_j].Y + normals[m_k].Y * delta)); 3556 IntPoint pt2 = new IntPoint(Round(pts[m_i][m_j].X + normals[m_j].X * delta), 3557 Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); 3558 AddPoint(pt1); 3559 //round off reflex angles (ie > 180 deg) unless almost flat (ie < 10deg). 3560 //cross product normals < 0 . angle > 180 deg. 3561 //dot product normals == 1 . no angle 3562 if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * delta >= 0) 3563 { 3564 if ((normals[m_j].X * normals[m_k].X + normals[m_j].Y * normals[m_k].Y) < 0.985) 3565 { 3566 double a1 = Math.Atan2(normals[m_k].Y, normals[m_k].X); 3567 double a2 = Math.Atan2(normals[m_j].Y, normals[m_j].X); 3568 if (delta > 0 && a2 < a1) a2 += Math.PI * 2; 3569 else if (delta < 0 && a2 > a1) a2 -= Math.PI * 2; 3570 Polygon arc = BuildArc(pts[m_i][m_j], a1, a2, delta); 3571 for (int m = 0; m < arc.Count; m++) 3572 AddPoint(arc[m]); 3573 } 3574 } 3575 else 3576 AddPoint(pts[m_i][m_j]); 3577 AddPoint(pt2); 3578 } 3579 //------------------------------------------------------------------------------ 3580 3581 } //end PolyOffsetBuilder 3582 //------------------------------------------------------------------------------ 3583 OffsetPolygons(Polygons poly, double delta, JoinType jointype, double MiterLimit)3584 public static Polygons OffsetPolygons(Polygons poly, double delta, 3585 JoinType jointype, double MiterLimit) 3586 { 3587 Polygons result = new Polygons(poly.Count); 3588 new PolyOffsetBuilder(poly, result, delta, jointype, MiterLimit); 3589 return result; 3590 } 3591 //------------------------------------------------------------------------------ 3592 OffsetPolygons(Polygons poly, double delta, JoinType jointype)3593 public static Polygons OffsetPolygons(Polygons poly, double delta, JoinType jointype) 3594 { 3595 Polygons result = new Polygons(poly.Count); 3596 new PolyOffsetBuilder(poly, result, delta, jointype, 2.0); 3597 return result; 3598 } 3599 //------------------------------------------------------------------------------ 3600 OffsetPolygons(Polygons poly, double delta)3601 public static Polygons OffsetPolygons(Polygons poly, double delta) 3602 { 3603 Polygons result = new Polygons(poly.Count); 3604 new PolyOffsetBuilder(poly, result, delta, JoinType.jtSquare, 2.0); 3605 return result; 3606 } 3607 3608 //------------------------------------------------------------------------------ 3609 // SimplifyPolygon functions ... 3610 // Convert self-intersecting polygons into simple polygons 3611 //------------------------------------------------------------------------------ 3612 SimplifyPolygon(Polygon poly)3613 public static Polygons SimplifyPolygon(Polygon poly) 3614 { 3615 Polygons result = new Polygons(); 3616 Clipper c = new Clipper(); 3617 c.AddPolygon(poly, PolyType.ptSubject); 3618 c.Execute(ClipType.ctUnion, result); 3619 return result; 3620 } 3621 //------------------------------------------------------------------------------ 3622 SimplifyPolygons(Polygons polys)3623 public static Polygons SimplifyPolygons(Polygons polys) 3624 { 3625 Polygons result = new Polygons(); 3626 Clipper c = new Clipper(); 3627 c.AddPolygons(polys, PolyType.ptSubject); 3628 c.Execute(ClipType.ctUnion, result); 3629 return result; 3630 } 3631 //------------------------------------------------------------------------------ 3632 3633 } //end ClipperLib namespace 3634 3635 class ClipperException : Exception 3636 { ClipperException(string description)3637 public ClipperException(string description) : base(description){} 3638 } 3639 //------------------------------------------------------------------------------ 3640 } 3641