1 unit clipper;
2
3 (*******************************************************************************
4 * *
5 * Author : Angus Johnson *
6 * Version : 6.4.2 *
7 * Date : 27 February 2017 *
8 * Website : http://www.angusj.com *
9 * Copyright : Angus Johnson 2010-2017 *
10 * *
11 * License: *
12 * Use, modification & distribution is subject to Boost Software License Ver 1. *
13 * http://www.boost.org/LICENSE_1_0.txt *
14 * *
15 * Attributions: *
16 * The code in this library is an extension of Bala Vatti's clipping algorithm: *
17 * "A generic solution to polygon clipping" *
18 * Communications of the ACM, Vol 35, Issue 7 (July 1992) PP 56-63. *
19 * http://portal.acm.org/citation.cfm?id=129906 *
20 * *
21 * Computer graphics and geometric modeling: implementation and algorithms *
22 * By Max K. Agoston *
23 * Springer; 1 edition (January 4, 2005) *
24 * http://books.google.com/books?q=vatti+clipping+agoston *
25 * *
26 * See also: *
27 * "Polygon Offsetting by Computing Winding Numbers" *
28 * Paper no. DETC2005-85513 PP. 565-575 *
29 * ASME 2005 International Design Engineering Technical Conferences *
30 * and Computers and Information in Engineering Conference (IDETC/CIE2005) *
31 * September 24-28, 2005 , Long Beach, California, USA *
32 * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
33 * *
34 *******************************************************************************)
35
36 //use_int32: When enabled 32bit ints are used instead of 64bit ints. This
37 //improve performance but coordinate values are limited to the range +/- 46340
38 //{$DEFINE use_int32}
39
40 //use_xyz: adds a Z member to IntPoint (with only a minor cost to performance)
41 //{$DEFINE use_xyz}
42
43 //use_lines: Enables open path clipping (with a very minor cost to performance)
44 {$DEFINE use_lines}
45
46 {$IFDEF FPC}
47 {$DEFINE INLINING}
48 {$DEFINE UInt64Support}
49 {$ELSE}
50 // enable LEGACYIFEND for Delphi XE4+
51 {$IF CompilerVersion >= 25.0}
52 {$LEGACYIFEND ON}
53 {$IFEND}
54
55 // use generic lists for NextGen compiler
56 {$IFDEF NEXTGEN}
57 {$DEFINE USEGENERICS}
58 {$ENDIF}
59
60 {$IFDEF ConditionalExpressions}
61 {$IF CompilerVersion >= 15} //Delphi 7
62 {$DEFINE UInt64Support} //nb: Delphi7 only marginally supports UInt64.
63 {$IFEND}
64 {$IF CompilerVersion >= 18} //Delphi 2007
65 //Inline has been supported since D2005.
66 //However D2005 and D2006 have an Inline codegen bug (QC41166).
67 //http://www.thedelphigeek.com/2007/02/nasty-inline-codegen-bug-in-bds-2006.html
68 {$DEFINE INLINING}
69 {$IFEND}
70 {$ENDIF}
71 {$ENDIF}
72
73 interface
74
75 uses
76 SysUtils, Types, Classes,
77 {$IFDEF USEGENERICS}
78 Generics.Collections, Generics.Defaults,
79 {$ENDIF}
80 Math;
81
82 const
83 def_arc_tolerance = 0.25;
84
85 type
86 {$IFDEF use_int32}
87 {$IF CompilerVersion < 20} //Delphi 2009
88 cInt = Integer; //Int32 supported since D2009.
89 {$ELSE}
90 cInt = Int32;
91 {$IFEND}
92 {$ELSE}
93 cInt = Int64;
94 {$ENDIF}
95
96 PIntPoint = ^TIntPoint;
97 {$IFDEF use_xyz}
98 TIntPoint = record X, Y, Z: cInt; end;
99 {$ELSE}
100 TIntPoint = record X, Y: cInt; end;
101 {$ENDIF}
102
103 TIntRect = record Left, Top, Right, Bottom: cInt; end;
104
105 TDoublePoint = record X, Y: Double; end;
106 TArrayOfDoublePoint = array of TDoublePoint;
107
108 {$IFDEF use_xyz}
109 TZFillCallback =
110 procedure (const E1Bot, E1Top, E2Bot, E2Top: TIntPoint; var Pt: TIntPoint);
111 {$ENDIF}
112
113 TInitOption = (ioReverseSolution, ioStrictlySimple, ioPreserveCollinear);
114 TInitOptions = set of TInitOption;
115
116 TClipType = (ctIntersection, ctUnion, ctDifference, ctXor);
117 TPolyType = (ptSubject, ptClip);
118 //By far the most widely used winding rules for polygon filling are
119 //EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
120 //Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
121 //see http://glprogramming.com/red/chapter11.html
122 TPolyFillType = (pftEvenOdd, pftNonZero, pftPositive, pftNegative);
123
124 //TJoinType & TEndType are used by OffsetPaths()
125 TJoinType = (jtSquare, jtRound, jtMiter);
126 TEndType = (etClosedPolygon, etClosedLine,
127 etOpenButt, etOpenSquare, etOpenRound); //and etSingle still to come
128
129 TPath = array of TIntPoint;
130 TPaths = array of TPath;
131
132 TPolyNode = class;
133 TArrayOfPolyNode = array of TPolyNode;
134
135 TPolyNode = class
136 private
137 FPath : TPath;
138 FParent : TPolyNode;
139 FIndex : Integer;
140 FCount : Integer;
141 FBuffLen : Integer;
142 FIsOpen : Boolean;
143 FChilds : TArrayOfPolyNode;
144 FJoinType: TJoinType; //used by ClipperOffset only
145 FEndType : TEndType; //used by ClipperOffset only
GetChildnull146 function GetChild(Index: Integer): TPolyNode;
IsHoleNodenull147 function IsHoleNode: boolean;
148 procedure AddChild(PolyNode: TPolyNode);
GetNextSiblingUpnull149 function GetNextSiblingUp: TPolyNode;
150 public
GetNextnull151 function GetNext: TPolyNode;
152 property ChildCount: Integer read FCount;
153 property Childs[index: Integer]: TPolyNode read GetChild;
154 property Parent: TPolyNode read FParent;
155 property IsHole: Boolean read IsHoleNode;
156 property IsOpen: Boolean read FIsOpen;
157 property Contour: TPath read FPath;
158 end;
159
160 TPolyTree = class(TPolyNode)
161 private
162 FAllNodes: TArrayOfPolyNode; //container for ALL PolyNodes
GetTotalnull163 function GetTotal: Integer;
164 public
165 procedure Clear;
GetFirstnull166 function GetFirst: TPolyNode;
167 destructor Destroy; override;
168 property Total: Integer read GetTotal;
169 end;
170
171 //the definitions below are used internally ...
172 TEdgeSide = (esLeft, esRight);
173 TDirection = (dRightToLeft, dLeftToRight);
174
175 POutPt = ^TOutPt;
176
177 PEdge = ^TEdge;
178 TEdge = record
179 Bot : TIntPoint; //bottom
180 Curr : TIntPoint; //current (updated for every new scanbeam)
181 Top : TIntPoint; //top
182 Dx : Double; //inverse of slope
183 PolyType : TPolyType;
184 Side : TEdgeSide; //side only refers to current side of solution poly
185 WindDelta: Integer; //1 or -1 depending on winding direction
186 WindCnt : Integer;
187 WindCnt2 : Integer; //winding count of the opposite PolyType
188 OutIdx : Integer;
189 Next : PEdge;
190 Prev : PEdge;
191 NextInLML: PEdge;
192 PrevInAEL: PEdge;
193 NextInAEL: PEdge;
194 PrevInSEL: PEdge;
195 NextInSEL: PEdge;
196 end;
197
198 PEdgeArray = ^TEdgeArray;
199 TEdgeArray = array[0.. MaxInt div sizeof(TEdge) -1] of TEdge;
200
201 PScanbeam = ^TScanbeam;
202 TScanbeam = record
203 Y : cInt;
204 Next : PScanbeam;
205 end;
206
207 PMaxima = ^TMaxima;
208 TMaxima = record
209 X : cInt;
210 Next : PMaxima;
211 Prev : PMaxima;
212 end;
213
214 PIntersectNode = ^TIntersectNode;
215 TIntersectNode = record
216 Edge1: PEdge;
217 Edge2: PEdge;
218 Pt : TIntPoint;
219 end;
220
221 PLocalMinimum = ^TLocalMinimum;
222 TLocalMinimum = record
223 Y : cInt;
224 LeftBound : PEdge;
225 RightBound: PEdge;
226 end;
227
228 //OutRec: contains a path in the clipping solution. Edges in the AEL will
229 //carry a pointer to an OutRec when they are part of the clipping solution.
230 POutRec = ^TOutRec;
231 TOutRec = record
232 Idx : Integer;
233 BottomPt : POutPt;
234 IsHole : Boolean;
235 IsOpen : Boolean;
236 //The 'FirstLeft' field points to another OutRec that contains or is the
237 //'parent' of OutRec. It is 'first left' because the ActiveEdgeList (AEL) is
238 //parsed left from the current edge (owning OutRec) until the owner OutRec
239 //is found. This field simplifies sorting the polygons into a tree structure
240 //which reflects the parent/child relationships of all polygons.
241 //This field should be renamed Parent, and will be later.
242 FirstLeft : POutRec;
243 Pts : POutPt;
244 PolyNode : TPolyNode;
245 end;
246
247 TOutPt = record
248 Idx : Integer;
249 Pt : TIntPoint;
250 Next : POutPt;
251 Prev : POutPt;
252 end;
253
254 PJoin = ^TJoin;
255 TJoin = record
256 OutPt1 : POutPt;
257 OutPt2 : POutPt;
258 OffPt : TIntPoint; //offset point (provides slope of common edges)
259 end;
260
261 {$IFDEF USEGENERICS}
262 TEgdeList = TList<PEdgeArray>;
263 TLocMinList = TList<PLocalMinimum>;
264 TPolyOutList = TList<POutRec>;
265 TJoinList = TList<PJoin>;
266 TIntersecList = TList<PIntersectNode>;
267 {$ELSE}
268 TEgdeList = TList;
269 TLocMinList = TList;
270 TPolyOutList = TList;
271 TJoinList = TList;
272 TIntersecList = TList;
273 {$ENDIF}
274
275 TClipperBase = class
276 private
277 FEdgeList : TEgdeList;
278 FPolyOutList : TPolyOutList;
279 FScanbeam : PScanbeam; //scanbeam list
280 FUse64BitRange : Boolean; //see LoRange and HiRange consts notes below
281 FHasOpenPaths : Boolean;
282 procedure DisposeLocalMinimaList;
283 procedure DisposePolyPts(PP: POutPt);
ProcessBoundnull284 function ProcessBound(E: PEdge; NextIsForward: Boolean): PEdge;
285 protected
286 FLocMinList : TLocMinList;
287 FCurrentLocMinIdx : Integer;
288 FPreserveCollinear : Boolean;
289 FActiveEdges : PEdge; //active Edge list
290 procedure Reset; virtual;
291 procedure InsertScanbeam(const Y: cInt);
PopScanbeamnull292 function PopScanbeam(out Y: cInt): Boolean;
LocalMinimaPendingnull293 function LocalMinimaPending: Boolean;
PopLocalMinimanull294 function PopLocalMinima(Y: cInt;
295 out LocalMinima: PLocalMinimum): Boolean;
296 procedure DisposeScanbeamList;
CreateOutRecnull297 function CreateOutRec: POutRec;
298 procedure DisposeOutRec(Index: Integer);
299 procedure DisposeAllOutRecs;
300 procedure SwapPositionsInAEL(E1, E2: PEdge);
301 procedure DeleteFromAEL(E: PEdge);
302 procedure UpdateEdgeIntoAEL(var E: PEdge);
303 property HasOpenPaths: Boolean read FHasOpenPaths;
304 public
305 constructor Create; virtual;
306 destructor Destroy; override;
307 procedure Clear; virtual;
308
AddPathnull309 function AddPath(const Path: TPath; PolyType: TPolyType; Closed: Boolean): Boolean; virtual;
AddPathsnull310 function AddPaths(const Paths: TPaths; PolyType: TPolyType; Closed: Boolean): Boolean;
311 //PreserveCollinear: Prevents removal of 'inner' vertices when three or
312 //more vertices are collinear in solution polygons.
313 property PreserveCollinear: Boolean
314 read FPreserveCollinear write FPreserveCollinear;
315 end;
316
317 TClipper = class(TClipperBase)
318 private
319 FJoinList : TJoinList;
320 FGhostJoinList : TJoinList;
321 FIntersectList : TIntersecList;
322 FSortedEdges : PEdge; //used for temporary sorting
323 FClipType : TClipType;
324 FMaxima : PMaxima; //maxima XPos list
325 FClipFillType : TPolyFillType;
326 FSubjFillType : TPolyFillType;
327 FExecuteLocked : Boolean;
328 FReverseOutput : Boolean;
329 FStrictSimple : Boolean;
330 FUsingPolyTree : Boolean;
331 {$IFDEF use_xyz}
332 FZFillCallback : TZFillCallback;
333 {$ENDIF}
334 procedure InsertMaxima(const X: cInt);
335 procedure DisposeMaximaList;
336 procedure SetWindingCount(Edge: PEdge);
IsEvenOddFillTypenull337 function IsEvenOddFillType(Edge: PEdge): Boolean;
IsEvenOddAltFillTypenull338 function IsEvenOddAltFillType(Edge: PEdge): Boolean;
339 procedure AddEdgeToSEL(Edge: PEdge);
PopEdgeFromSELnull340 function PopEdgeFromSEL(out E: PEdge): Boolean;
341 procedure CopyAELToSEL;
342 procedure InsertLocalMinimaIntoAEL(const BotY: cInt);
343 procedure SwapPositionsInSEL(E1, E2: PEdge);
344 procedure ProcessHorizontal(HorzEdge: PEdge);
345 procedure ProcessHorizontals;
ProcessIntersectionsnull346 function ProcessIntersections(const TopY: cInt): Boolean;
347 procedure BuildIntersectList(const TopY: cInt);
348 procedure ProcessIntersectList;
349 procedure IntersectEdges(E1,E2: PEdge; Pt: TIntPoint);
350 procedure DoMaxima(E: PEdge);
FixupIntersectionOrdernull351 function FixupIntersectionOrder: Boolean;
352 procedure ProcessEdgesAtTopOfScanbeam(const TopY: cInt);
IsContributingnull353 function IsContributing(Edge: PEdge): Boolean;
GetLastOutPtnull354 function GetLastOutPt(E: PEdge): POutPt;
355 procedure AddLocalMaxPoly(E1, E2: PEdge; const Pt: TIntPoint);
AddLocalMinPolynull356 function AddLocalMinPoly(E1, E2: PEdge; const Pt: TIntPoint): POutPt;
AddOutPtnull357 function AddOutPt(E: PEdge; const Pt: TIntPoint): POutPt;
GetOutRecnull358 function GetOutRec(Idx: integer): POutRec;
359 procedure AppendPolygon(E1, E2: PEdge);
360 procedure DisposeIntersectNodes;
BuildResultnull361 function BuildResult: TPaths;
BuildResult2null362 function BuildResult2(PolyTree: TPolyTree): Boolean;
363 procedure FixupOutPolygon(OutRec: POutRec);
364 procedure FixupOutPolyline(OutRec: POutRec);
365 procedure SetHoleState(E: PEdge; OutRec: POutRec);
366 procedure AddJoin(Op1, Op2: POutPt; const OffPt: TIntPoint);
367 procedure ClearJoins;
368 procedure AddGhostJoin(OutPt: POutPt; const OffPt: TIntPoint);
369 procedure ClearGhostJoins;
JoinPointsnull370 function JoinPoints(Jr: PJoin; OutRec1, OutRec2: POutRec): Boolean;
371 procedure FixupFirstLefts1(OldOutRec, NewOutRec: POutRec);
372 procedure FixupFirstLefts2(InnerOutRec, OuterOutRec: POutRec);
373 procedure FixupFirstLefts3(OldOutRec, NewOutRec: POutRec);
374 procedure DoSimplePolygons;
375 procedure JoinCommonEdges;
376 procedure FixHoleLinkage(OutRec: POutRec);
377 protected
ExecuteInternalnull378 function ExecuteInternal: Boolean; virtual;
379 public
Executenull380 function Execute(clipType: TClipType;
381 out solution: TPaths;
382 FillType: TPolyFillType = pftEvenOdd): Boolean; overload;
Executenull383 function Execute(clipType: TClipType;
384 out solution: TPaths;
385 subjFillType: TPolyFillType;
386 clipFillType: TPolyFillType): Boolean; overload;
Executenull387 function Execute(clipType: TClipType;
388 out PolyTree: TPolyTree;
389 FillType: TPolyFillType = pftEvenOdd): Boolean; overload;
Executenull390 function Execute(clipType: TClipType;
391 out PolyTree: TPolyTree;
392 subjFillType: TPolyFillType;
393 clipFillType: TPolyFillType): Boolean; overload;
394 constructor Create(InitOptions: TInitOptions = []); reintroduce; overload;
395 destructor Destroy; override;
396 //ReverseSolution: reverses the default orientation
397 property ReverseSolution: Boolean read FReverseOutput write FReverseOutput;
398 //StrictlySimple: when false (the default) solutions are 'weakly' simple
399 property StrictlySimple: Boolean read FStrictSimple write FStrictSimple;
400 {$IFDEF use_xyz}
TZFillCallbacknull401 property ZFillFunction: TZFillCallback read FZFillCallback write FZFillCallback;
402 {$ENDIF}
403 end;
404
405 TClipperOffset = class
406 private
407 FDelta: Double;
408 FSinA, FSin, FCos: Extended;
409 FMiterLim, FStepsPerRad: Double;
410 FNorms: TArrayOfDoublePoint;
411 FSolution: TPaths;
412 FOutPos: Integer;
413 FInP: TPath;
414 FOutP: TPath;
415
416 FLowest: TIntPoint; //X = Path index, Y = Path offset (to lowest point)
417 FPolyNodes: TPolyNode;
418 FMiterLimit: Double;
419 FArcTolerance: Double;
420
421 procedure AddPoint(const Pt: TIntPoint);
422 procedure DoSquare(J, K: Integer);
423 procedure DoMiter(J, K: Integer; R: Double);
424 procedure DoRound(J, K: Integer);
425 procedure OffsetPoint(J: Integer;
426 var K: Integer; JoinType: TJoinType);
427
428 procedure FixOrientations;
429 procedure DoOffset(Delta: Double);
430 public
431 constructor Create(MiterLimit: Double = 2; ArcTolerance: Double = def_arc_tolerance);
432 destructor Destroy; override;
433 procedure AddPath(const Path: TPath; JoinType: TJoinType; EndType: TEndType);
434 procedure AddPaths(const Paths: TPaths; JoinType: TJoinType; EndType: TEndType);
435 procedure Clear;
436 procedure Execute(out solution: TPaths; Delta: Double); overload;
437 procedure Execute(out solution: TPolyTree; Delta: Double); overload;
438 property MiterLimit: double read FMiterLimit write FMiterLimit;
439 property ArcTolerance: double read FArcTolerance write FArcTolerance;
440
441 end;
442
Orientationnull443 function Orientation(const Pts: TPath): Boolean; overload;
Areanull444 function Area(const Pts: TPath): Double; overload;
PointInPolygonnull445 function PointInPolygon (const pt: TIntPoint; const poly: TPath): Integer; overload;
GetBoundsnull446 function GetBounds(const polys: TPaths): TIntRect;
447
448 {$IFDEF use_xyz}
IntPointnull449 function IntPoint(const X, Y: Int64; Z: Int64 = 0): TIntPoint; overload;
IntPointnull450 function IntPoint(const X, Y: Double; Z: Double = 0): TIntPoint; overload;
451 {$ELSE}
IntPointnull452 function IntPoint(const X, Y: cInt): TIntPoint; overload;
IntPointnull453 function IntPoint(const X, Y: Double): TIntPoint; overload;
454 {$ENDIF}
455
DoublePointnull456 function DoublePoint(const X, Y: Double): TDoublePoint; overload;
DoublePointnull457 function DoublePoint(const Ip: TIntPoint): TDoublePoint; overload;
458
ReversePathnull459 function ReversePath(const Pts: TPath): TPath;
ReversePathsnull460 function ReversePaths(const Pts: TPaths): TPaths;
461
462 //SimplifyPolygon converts a self-intersecting polygon into a simple polygon.
SimplifyPolygonnull463 function SimplifyPolygon(const Poly: TPath; FillType: TPolyFillType = pftEvenOdd): TPaths;
SimplifyPolygonsnull464 function SimplifyPolygons(const Polys: TPaths; FillType: TPolyFillType = pftEvenOdd): TPaths;
465
466 //CleanPolygon removes adjacent vertices closer than the specified distance.
CleanPolygonnull467 function CleanPolygon(const Poly: TPath; Distance: double = 1.415): TPath;
CleanPolygonsnull468 function CleanPolygons(const Polys: TPaths; Distance: double = 1.415): TPaths;
469
MinkowskiSumnull470 function MinkowskiSum(const Pattern, Path: TPath; PathIsClosed: Boolean): TPaths; overload;
MinkowskiSumnull471 function MinkowskiSum(const Pattern: TPath; const Paths: TPaths;
472 PathFillType: TPolyFillType; PathIsClosed: Boolean): TPaths; overload;
MinkowskiDiffnull473 function MinkowskiDiff(const Poly1, Poly2: TPath): TPaths;
474
PolyTreeToPathsnull475 function PolyTreeToPaths(PolyTree: TPolyTree): TPaths;
ClosedPathsFromPolyTreenull476 function ClosedPathsFromPolyTree(PolyTree: TPolyTree): TPaths;
OpenPathsFromPolyTreenull477 function OpenPathsFromPolyTree(PolyTree: TPolyTree): TPaths;
478
479 const
placesnull480 //The SlopesEqual function places the most limits on coordinate values
481 //So, to avoid overflow errors, they must not exceed the following values...
482 //Also, if all coordinates are within +/-LoRange, then calculations will be
483 //faster. Otherwise using Int128 math will render the library ~10-15% slower.
484 {$IFDEF use_int32}
485 LoRange: cInt = 46340;
486 HiRange: cInt = 46340;
487 {$ELSE}
488 LoRange: cInt = $B504F333; //3.0e+9
489 HiRange: cInt = $3FFFFFFFFFFFFFFF; //9.2e+18
490 {$ENDIF}
491
492 implementation
493
494 //NOTE: The Clipper library has been developed with software that uses an
495 //inverted Y axis display. Therefore 'above' and 'below' in the code's comments
496 //will reflect this. For example: given coord A (0,20) and coord B (0,10),
497 //A.Y would be considered BELOW B.Y to correctly understand the comments.
498
499 const
500 Horizontal: Double = -3.4e+38;
501
502 Unassigned : Integer = -1;
503 Skip : Integer = -2; //flag for the edge that closes an open path
504 Tolerance : double = 1.0E-15;
505 Two_Pi : double = 2 * PI;
506
507 resourcestring
508 rsDoMaxima = 'DoMaxima error';
509 rsUpdateEdgeIntoAEL = 'UpdateEdgeIntoAEL error';
510 rsHorizontal = 'ProcessHorizontal error';
511 rsInvalidInt = 'Coordinate exceeds range bounds';
512 rsIntersect = 'Intersection error';
513 rsOpenPath = 'AddPath: Open paths must be subject.';
514 rsOpenPath2 = 'AddPath: Open paths have been disabled.';
515 rsOpenPath3 = 'Error: TPolyTree struct is needed for open path clipping.';
516 rsPolylines = 'Error intersecting polylines';
517 rsClipperOffset = 'Error: No PolyTree assigned';
518
519 //------------------------------------------------------------------------------
520 // TPolyNode methods ...
521 //------------------------------------------------------------------------------
522
GetChildnull523 function TPolyNode.GetChild(Index: Integer): TPolyNode;
524 begin
525 if (Index < 0) or (Index >= FCount) then
526 raise Exception.Create('TPolyNode range error: ' + inttostr(Index));
527 Result := FChilds[Index];
528 end;
529 //------------------------------------------------------------------------------
530
531 procedure TPolyNode.AddChild(PolyNode: TPolyNode);
532 begin
533 if FCount = FBuffLen then
534 begin
535 Inc(FBuffLen, 16);
536 SetLength(FChilds, FBuffLen);
537 end;
538 PolyNode.FParent := self;
539 PolyNode.FIndex := FCount;
540 FChilds[FCount] := PolyNode;
541 Inc(FCount);
542 end;
543 //------------------------------------------------------------------------------
544
IsHoleNodenull545 function TPolyNode.IsHoleNode: boolean;
546 var
547 Node: TPolyNode;
548 begin
549 Result := True;
550 Node := FParent;
551 while Assigned(Node) do
552 begin
553 Result := not Result;
554 Node := Node.FParent;
555 end;
556 end;
557 //------------------------------------------------------------------------------
558
GetNextnull559 function TPolyNode.GetNext: TPolyNode;
560 begin
561 if FCount > 0 then
562 Result := FChilds[0] else
563 Result := GetNextSiblingUp;
564 end;
565 //------------------------------------------------------------------------------
566
GetNextSiblingUpnull567 function TPolyNode.GetNextSiblingUp: TPolyNode;
568 begin
569 if not Assigned(FParent) then //protects against TPolyTree.GetNextSiblingUp()
570 Result := nil
571 else if FIndex = FParent.FCount -1 then
572 Result := FParent.GetNextSiblingUp
573 else
574 Result := FParent.Childs[FIndex +1];
575 end;
576
577 //------------------------------------------------------------------------------
578 // TPolyTree methods ...
579 //------------------------------------------------------------------------------
580
581 destructor TPolyTree.Destroy;
582 begin
583 Clear;
584 inherited;
585 end;
586 //------------------------------------------------------------------------------
587
588 procedure TPolyTree.Clear;
589 var
590 I: Integer;
591 begin
592 for I := 0 to high(FAllNodes) do FAllNodes[I].Free;
593 FAllNodes := nil;
594 FBuffLen := 16;
595 SetLength(FChilds, FBuffLen);
596 FCount := 0;
597 end;
598 //------------------------------------------------------------------------------
599
GetFirstnull600 function TPolyTree.GetFirst: TPolyNode;
601 begin
602 if FCount > 0 then
603 Result := FChilds[0] else
604 Result := nil;
605 end;
606 //------------------------------------------------------------------------------
607
GetTotalnull608 function TPolyTree.GetTotal: Integer;
609 begin
610 Result := length(FAllNodes);
611 //with negative offsets, ignore the hidden outer polygon ...
612 if (Result > 0) and (FAllNodes[0] <> FChilds[0]) then dec(Result);
613 end;
614
615 {$IFNDEF use_int32}
616
617 //------------------------------------------------------------------------------
618 // UInt64 math support for Delphi 6
619 //------------------------------------------------------------------------------
620
621 {$OVERFLOWCHECKS OFF}
622 {$IFNDEF UInt64Support}
623 function CompareUInt64(const i, j: Int64): Integer;
624 begin
625 if Int64Rec(i).Hi < Int64Rec(j).Hi then
626 Result := -1
627 else if Int64Rec(i).Hi > Int64Rec(j).Hi then
628 Result := 1
629 else if Int64Rec(i).Lo < Int64Rec(j).Lo then
630 Result := -1
631 else if Int64Rec(i).Lo > Int64Rec(j).Lo then
632 Result := 1
633 else
634 Result := 0;
635 end;
636 {$ENDIF}
637
638 function UInt64LT(const i, j: Int64): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
639 begin
640 {$IFDEF UInt64Support}
641 Result := UInt64(i) < UInt64(j);
642 {$ELSE}
643 Result := CompareUInt64(i, j) = -1;
644 {$ENDIF}
645 end;
646
647 function UInt64GT(const i, j: Int64): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
648 begin
649 {$IFDEF UInt64Support}
650 Result := UInt64(i) > UInt64(j);
651 {$ELSE}
652 Result := CompareUInt64(i, j) = 1;
653 {$ENDIF}
654 end;
655 {$OVERFLOWCHECKS ON}
656
657 //------------------------------------------------------------------------------
658 // Int128 Functions ...
659 //------------------------------------------------------------------------------
660
661 const
662 Mask32Bits = $FFFFFFFF;
663
664 type
665
666 //nb: TInt128.Lo is typed Int64 instead of UInt64 to provide Delphi 7
667 //compatability. However while UInt64 isn't a recognised type in
668 //Delphi 7, it can still be used in typecasts.
669 TInt128 = record
670 Hi : Int64;
671 Lo : Int64;
672 end;
673
674 {$OVERFLOWCHECKS OFF}
675 procedure Int128Negate(var Val: TInt128);
676 begin
677 if Val.Lo = 0 then
678 begin
679 Val.Hi := -Val.Hi;
680 end else
681 begin
682 Val.Lo := -Val.Lo;
683 Val.Hi := not Val.Hi;
684 end;
685 end;
686 //------------------------------------------------------------------------------
687
Int128null688 function Int128(const val: Int64): TInt128; overload;
689 begin
690 Result.Lo := val;
691 if val < 0 then
692 Result.Hi := -1 else
693 Result.Hi := 0;
694 end;
695 //------------------------------------------------------------------------------
696
Int128Equalnull697 function Int128Equal(const Int1, Int2: TInt128): Boolean;
698 begin
699 Result := (Int1.Lo = Int2.Lo) and (Int1.Hi = Int2.Hi);
700 end;
701 //------------------------------------------------------------------------------
702
Int128LessThannull703 function Int128LessThan(const Int1, Int2: TInt128): Boolean;
704 begin
705 if (Int1.Hi <> Int2.Hi) then Result := Int1.Hi < Int2.Hi
706 else Result := UInt64LT(Int1.Lo, Int2.Lo);
707 end;
708 //------------------------------------------------------------------------------
709
Int128IsNegativenull710 function Int128IsNegative(const Int: TInt128): Boolean;
711 begin
712 Result := Int.Hi < 0;
713 end;
714 //------------------------------------------------------------------------------
715
Int128IsPositivenull716 function Int128IsPositive(const Int: TInt128): Boolean;
717 begin
718 Result := (Int.Hi > 0) or ((Int.Hi = 0) and (Int.Lo <> 0));
719 end;
720 //------------------------------------------------------------------------------
721
Int128Addnull722 function Int128Add(const Int1, Int2: TInt128): TInt128;
723 begin
724 Result.Lo := Int1.Lo + Int2.Lo;
725 Result.Hi := Int1.Hi + Int2.Hi;
726 if UInt64LT(Result.Lo, Int1.Lo) then Inc(Result.Hi);
727 end;
728 //------------------------------------------------------------------------------
729
Int128Subnull730 function Int128Sub(const Int1, Int2: TInt128): TInt128;
731 begin
732 Result.Hi := Int1.Hi - Int2.Hi;
733 Result.Lo := Int1.Lo - Int2.Lo;
734 if UInt64GT(Result.Lo, Int1.Lo) then Dec(Result.Hi);
735 end;
736 //------------------------------------------------------------------------------
737
Int128Mulnull738 function Int128Mul(Int1, Int2: Int64): TInt128;
739 var
740 A, B, C: Int64;
741 Int1Hi, Int1Lo, Int2Hi, Int2Lo: Int64;
742 Negate: Boolean;
743 begin
744 //save the Result's sign before clearing both sign bits ...
745 Negate := (Int1 < 0) <> (Int2 < 0);
746 if Int1 < 0 then Int1 := -Int1;
747 if Int2 < 0 then Int2 := -Int2;
748
749 Int1Hi := Int1 shr 32;
750 Int1Lo := Int1 and Mask32Bits;
751 Int2Hi := Int2 shr 32;
752 Int2Lo := Int2 and Mask32Bits;
753
754 A := Int1Hi * Int2Hi;
755 B := Int1Lo * Int2Lo;
756 //because the high (sign) bits in both int1Hi & int2Hi have been zeroed,
757 //there's no risk of 64 bit overflow in the following assignment
758 //(ie: $7FFFFFFF*$FFFFFFFF + $7FFFFFFF*$FFFFFFFF < 64bits)
759 C := Int1Hi*Int2Lo + Int2Hi*Int1Lo;
760 //Result = A shl 64 + C shl 32 + B ...
761 Result.Hi := A + (C shr 32);
762 A := C shl 32;
763
764 Result.Lo := A + B;
765 if UInt64LT(Result.Lo, A) then
766 Inc(Result.Hi);
767
768 if Negate then Int128Negate(Result);
769 end;
770 //------------------------------------------------------------------------------
771
Int128Divnull772 function Int128Div(Dividend, Divisor: TInt128{; out Remainder: TInt128}): TInt128;
773 var
774 Cntr: TInt128;
775 Negate: Boolean;
776 begin
777 if (Divisor.Lo = 0) and (Divisor.Hi = 0) then
778 raise Exception.create('int128Div error: divide by zero');
779
780 Negate := (Divisor.Hi < 0) <> (Dividend.Hi < 0);
781 if Dividend.Hi < 0 then Int128Negate(Dividend);
782 if Divisor.Hi < 0 then Int128Negate(Divisor);
783
784 if Int128LessThan(Divisor, Dividend) then
785 begin
786 Result.Hi := 0;
787 Result.Lo := 0;
788 Cntr.Lo := 1;
789 Cntr.Hi := 0;
790 //while (Dividend >= Divisor) do
791 while not Int128LessThan(Dividend, Divisor) do
792 begin
793 //divisor := divisor shl 1;
794 Divisor.Hi := Divisor.Hi shl 1;
795 if Divisor.Lo < 0 then Inc(Divisor.Hi);
796 Divisor.Lo := Divisor.Lo shl 1;
797
798 //Cntr := Cntr shl 1;
799 Cntr.Hi := Cntr.Hi shl 1;
800 if Cntr.Lo < 0 then Inc(Cntr.Hi);
801 Cntr.Lo := Cntr.Lo shl 1;
802 end;
803 //Divisor := Divisor shr 1;
804 Divisor.Lo := Divisor.Lo shr 1;
805 if Divisor.Hi and $1 = $1 then
806 Int64Rec(Divisor.Lo).Hi := Cardinal(Int64Rec(Divisor.Lo).Hi) or $80000000;
807 Divisor.Hi := Divisor.Hi shr 1;
808
809 //Cntr := Cntr shr 1;
810 Cntr.Lo := Cntr.Lo shr 1;
811 if Cntr.Hi and $1 = $1 then
812 Int64Rec(Cntr.Lo).Hi := Cardinal(Int64Rec(Cntr.Lo).Hi) or $80000000;
813 Cntr.Hi := Cntr.Hi shr 1;
814
815 //while (Cntr > 0) do
816 while not ((Cntr.Hi = 0) and (Cntr.Lo = 0)) do
817 begin
818 //if ( Dividend >= Divisor) then
819 if not Int128LessThan(Dividend, Divisor) then
820 begin
821 //Dividend := Dividend - Divisor;
822 Dividend := Int128Sub(Dividend, Divisor);
823
824 //Result := Result or Cntr;
825 Result.Hi := Result.Hi or Cntr.Hi;
826 Result.Lo := Result.Lo or Cntr.Lo;
827 end;
828 //Divisor := Divisor shr 1;
829 Divisor.Lo := Divisor.Lo shr 1;
830 if Divisor.Hi and $1 = $1 then
831 Int64Rec(Divisor.Lo).Hi := Cardinal(Int64Rec(Divisor.Lo).Hi) or $80000000;
832 Divisor.Hi := Divisor.Hi shr 1;
833
834 //Cntr := Cntr shr 1;
835 Cntr.Lo := Cntr.Lo shr 1;
836 if Cntr.Hi and $1 = $1 then
837 Int64Rec(Cntr.Lo).Hi := Cardinal(Int64Rec(Cntr.Lo).Hi) or $80000000;
838 Cntr.Hi := Cntr.Hi shr 1;
839 end;
840 if Negate then Int128Negate(Result);
841 //Remainder := Dividend;
842 end
843 else if (Divisor.Hi = Dividend.Hi) and (Divisor.Lo = Dividend.Lo) then
844 begin
845 if Negate then Result := Int128(-1) else Result := Int128(1);
846 end else
847 begin
848 Result := Int128(0);
849 end;
850 end;
851 //------------------------------------------------------------------------------
852
Int128AsDoublenull853 function Int128AsDouble(val: TInt128): Double;
854 const
855 shift64: Double = 18446744073709551616.0;
856 var
857 lo: Int64;
858 begin
859 if (val.Hi < 0) then
860 begin
861 lo := -val.Lo;
862 if lo = 0 then
863 Result := val.Hi * shift64 else
864 Result := -(not val.Hi * shift64 + UInt64(lo));
865 end else
866 Result := val.Hi * shift64 + UInt64(val.Lo);
867 end;
868 //------------------------------------------------------------------------------
869
870 {$OVERFLOWCHECKS ON}
871
872 {$ENDIF}
873
874 //------------------------------------------------------------------------------
875 // Miscellaneous Functions ...
876 //------------------------------------------------------------------------------
877
PointCountnull878 function PointCount(Pts: POutPt): Integer;
879 var
880 P: POutPt;
881 begin
882 Result := 0;
883 if not Assigned(Pts) then Exit;
884 P := Pts;
885 repeat
886 Inc(Result);
887 P := P.Next;
888 until P = Pts;
889 end;
890 //------------------------------------------------------------------------------
891
PointsEqualnull892 function PointsEqual(const P1, P2: TIntPoint): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
893 begin
894 Result := (P1.X = P2.X) and (P1.Y = P2.Y);
895 end;
896 //------------------------------------------------------------------------------
897
898 {$IFDEF use_xyz}
IntPointnull899 function IntPoint(const X, Y: Int64; Z: Int64 = 0): TIntPoint;
900 begin
901 Result.X := X;
902 Result.Y := Y;
903 Result.Z := Z;
904 end;
905 //------------------------------------------------------------------------------
906
IntPointnull907 function IntPoint(const X, Y: Double; Z: Double = 0): TIntPoint;
908 begin
909 Result.X := Round(X);
910 Result.Y := Round(Y);
911 Result.Z := Round(Z);
912 end;
913 //------------------------------------------------------------------------------
914
915 {$ELSE}
916
IntPointnull917 function IntPoint(const X, Y: cInt): TIntPoint;
918 begin
919 Result.X := X;
920 Result.Y := Y;
921 end;
922 //------------------------------------------------------------------------------
923
IntPointnull924 function IntPoint(const X, Y: Double): TIntPoint;
925 begin
926 Result.X := Round(X);
927 Result.Y := Round(Y);
928 end;
929
930 {$ENDIF}
931 //------------------------------------------------------------------------------
932
DoublePointnull933 function DoublePoint(const X, Y: Double): TDoublePoint;
934 begin
935 Result.X := X;
936 Result.Y := Y;
937 end;
938 //------------------------------------------------------------------------------
939
DoublePointnull940 function DoublePoint(const Ip: TIntPoint): TDoublePoint;
941 begin
942 Result.X := Ip.X;
943 Result.Y := Ip.Y;
944 end;
945 //------------------------------------------------------------------------------
946
Areanull947 function Area(const Pts: TPath): Double;
948 var
949 I, J, Cnt: Integer;
950 D: Double;
951 begin
952 Result := 0.0;
953 Cnt := Length(Pts);
954 if (Cnt < 3) then Exit;
955 J := cnt - 1;
956 for I := 0 to Cnt -1 do
957 begin
958 D := (Pts[j].X + Pts[i].X);
959 Result := Result + D * (Pts[j].Y - Pts[i].Y);
960 J := I;
961 end;
962 Result := -Result * 0.5;
963 end;
964 //------------------------------------------------------------------------------
965
Areanull966 function Area(Op: POutPt): Double; overload;
967 var
968 op2: POutPt;
969 d2: Double;
970 begin
971 Result := 0;
972 op2 := op;
973 if Assigned(op2) then
974 repeat
975 d2 := op2.Prev.Pt.X + op2.Pt.X;
976 Result := Result + d2 * (op2.Prev.Pt.Y - op2.Pt.Y);
977 op2 := op2.Next;
978 until op2 = op;
979 Result := Result * 0.5;
980 end;
981 //------------------------------------------------------------------------------
982
Areanull983 function Area(OutRec: POutRec): Double; overload;
984 begin
985 result := Area(OutRec.Pts);
986 end;
987 //------------------------------------------------------------------------------
988
Orientationnull989 function Orientation(const Pts: TPath): Boolean;
990 begin
991 Result := Area(Pts) >= 0;
992 end;
993 //------------------------------------------------------------------------------
994
ReversePathnull995 function ReversePath(const Pts: TPath): TPath;
996 var
997 I, HighI: Integer;
998 begin
999 HighI := high(Pts);
1000 SetLength(Result, HighI +1);
1001 for I := 0 to HighI do
1002 Result[I] := Pts[HighI - I];
1003 end;
1004 //------------------------------------------------------------------------------
1005
ReversePathsnull1006 function ReversePaths(const Pts: TPaths): TPaths;
1007 var
1008 I, J, highJ: Integer;
1009 begin
1010 I := length(Pts);
1011 SetLength(Result, I);
1012 for I := 0 to I -1 do
1013 begin
1014 highJ := high(Pts[I]);
1015 SetLength(Result[I], highJ+1);
1016 for J := 0 to highJ do
1017 Result[I][J] := Pts[I][highJ - J];
1018 end;
1019 end;
1020 //------------------------------------------------------------------------------
1021
GetBoundsnull1022 function GetBounds(const polys: TPaths): TIntRect;
1023 var
1024 I,J,Len: Integer;
1025 begin
1026 Len := Length(polys);
1027 I := 0;
1028 while (I < Len) and (Length(polys[I]) = 0) do inc(I);
1029 if (I = Len) then
1030 begin
1031 with Result do begin Left := 0; Top := 0; Right := 0; Bottom := 0; end;
1032 Exit;
1033 end;
1034 Result.Left := polys[I][0].X;
1035 Result.Right := Result.Left;
1036 Result.Top := polys[I][0].Y;
1037 Result.Bottom := Result.Top;
1038 for I := I to Len -1 do
1039 for J := 0 to High(polys[I]) do
1040 begin
1041 if polys[I][J].X < Result.Left then Result.Left := polys[I][J].X
1042 else if polys[I][J].X > Result.Right then Result.Right := polys[I][J].X;
1043 if polys[I][J].Y < Result.Top then Result.Top := polys[I][J].Y
1044 else if polys[I][J].Y > Result.Bottom then Result.Bottom := polys[I][J].Y;
1045 end;
1046 end;
1047 //------------------------------------------------------------------------------
1048
PointInPolygonnull1049 function PointInPolygon (const pt: TIntPoint; const poly: TPath): Integer;
1050 var
1051 i, cnt: Integer;
1052 d, d2, d3: double; //use cInt ???
1053 ip, ipNext: TIntPoint;
1054 begin
1055 //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
1056 //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
1057 //nb: if poly bounds are known, test them first before calling this function.
1058 result := 0;
1059 cnt := Length(poly);
1060 if cnt < 3 then Exit;
1061 ip := poly[0];
1062 for i := 1 to cnt do
1063 begin
1064 if i < cnt then ipNext := poly[i]
1065 else ipNext := poly[0];
1066
1067 if (ipNext.Y = pt.Y) then
1068 begin
1069 if (ipNext.X = pt.X) or ((ip.Y = pt.Y) and
1070 ((ipNext.X > pt.X) = (ip.X < pt.X))) then
1071 begin
1072 result := -1;
1073 Exit;
1074 end;
1075 end;
1076
1077 if ((ip.Y < pt.Y) <> (ipNext.Y < pt.Y)) then
1078 begin
1079 if (ip.X >= pt.X) then
1080 begin
1081 if (ipNext.X > pt.X) then
1082 result := 1 - result
1083 else
1084 begin
1085 d2 := (ip.X - pt.X);
1086 d3 := (ipNext.X - pt.X);
1087 d := d2 * (ipNext.Y - pt.Y) - d3 * (ip.Y - pt.Y);
1088 if (d = 0) then begin result := -1; Exit; end;
1089 if ((d > 0) = (ipNext.Y > ip.Y)) then
1090 result := 1 - result;
1091 end;
1092 end else
1093 begin
1094 if (ipNext.X > pt.X) then
1095 begin
1096 d2 := (ip.X - pt.X);
1097 d3 := (ipNext.X - pt.X);
1098 d := d2 * (ipNext.Y - pt.Y) - d3 * (ip.Y - pt.Y);
1099 if (d = 0) then begin result := -1; Exit; end;
1100 if ((d > 0) = (ipNext.Y > ip.Y)) then
1101 result := 1 - result;
1102 end;
1103 end;
1104 end;
1105 ip := ipNext;
1106 end;
1107 end;
1108 //---------------------------------------------------------------------------
1109
1110 //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
1111 //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
PointInPolygonnull1112 function PointInPolygon (const pt: TIntPoint; ops: POutPt): Integer; overload;
1113 var
1114 d, d2, d3: double; //nb: double not cInt avoids potential overflow errors
1115 opStart: POutPt;
1116 pt1, ptN: TIntPoint;
1117 begin
1118 //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
1119 result := 0;
1120 opStart := ops;
1121 pt1.X := ops.Pt.X; pt1.Y := ops.Pt.Y;
1122 repeat
1123 ops := ops.Next;
1124 ptN.X := ops.Pt.X; ptN.Y := ops.Pt.Y;
1125
1126 if (ptN.Y = pt.Y) then
1127 begin
1128 if (ptN.X = pt.X) or ((pt1.Y = pt.Y) and
1129 ((ptN.X > pt.X) = (pt1.X < pt.X))) then
1130 begin
1131 result := -1;
1132 Exit;
1133 end;
1134 end;
1135
1136 if ((pt1.Y < pt.Y) <> (ptN.Y < pt.Y)) then
1137 begin
1138 if (pt1.X >= pt.X) then
1139 begin
1140 if (ptN.X > pt.X) then
1141 result := 1 - result
1142 else
1143 begin
1144 d2 := (pt1.X - pt.X);
1145 d3 := (ptN.X - pt.X);
1146 d := d2 * (ptN.Y - pt.Y) - d3 * (pt1.Y - pt.Y);
1147 if (d = 0) then begin result := -1; Exit; end;
1148 if ((d > 0) = (ptN.Y > pt1.Y)) then
1149 result := 1 - result;
1150 end;
1151 end else
1152 begin
1153 if (ptN.X > pt.X) then
1154 begin
1155 d2 := (pt1.X - pt.X);
1156 d3 := (ptN.X - pt.X);
1157 d := d2 * (ptN.Y - pt.Y) - d3 * (pt1.Y - pt.Y);
1158 if (d = 0) then begin result := -1; Exit; end;
1159 if ((d > 0) = (ptN.Y > pt1.Y)) then
1160 result := 1 - result;
1161 end;
1162 end;
1163 end;
1164 pt1 := ptN;
1165 until ops = opStart;
1166 end;
1167 //---------------------------------------------------------------------------
1168
Poly2ContainsPoly1null1169 function Poly2ContainsPoly1(OutPt1, OutPt2: POutPt): Boolean;
1170 var
1171 res: integer;
1172 op: POutPt;
1173 begin
1174 op := OutPt1;
1175 repeat
1176 //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
1177 res := PointInPolygon(op.Pt, OutPt2);
1178 if (res >= 0) then
1179 begin
1180 Result := res > 0;
1181 Exit;
1182 end;
1183 op := op.Next;
1184 until op = OutPt1;
1185 Result := true; //all points on line => result = true
1186 end;
1187 //---------------------------------------------------------------------------
1188
SlopesEqualnull1189 function SlopesEqual(E1, E2: PEdge;
1190 UseFullInt64Range: Boolean): Boolean; overload;
1191 begin
1192 {$IFNDEF use_int32}
1193 if UseFullInt64Range then
1194 Result := Int128Equal(Int128Mul(E1.Top.Y-E1.Bot.Y, E2.Top.X-E2.Bot.X),
1195 Int128Mul(E1.Top.X-E1.Bot.X, E2.Top.Y-E2.Bot.Y))
1196 else
1197 {$ENDIF}
1198 Result := (E1.Top.Y-E1.Bot.Y) * (E2.Top.X-E2.Bot.X) =
1199 (E1.Top.X-E1.Bot.X) * (E2.Top.Y-E2.Bot.Y);
1200 end;
1201 //---------------------------------------------------------------------------
1202
SlopesEqualnull1203 function SlopesEqual(const Pt1, Pt2, Pt3: TIntPoint;
1204 UseFullInt64Range: Boolean): Boolean; overload;
1205 begin
1206 {$IFNDEF use_int32}
1207 if UseFullInt64Range then
1208 Result := Int128Equal(
1209 Int128Mul(Pt1.Y-Pt2.Y, Pt2.X-Pt3.X), Int128Mul(Pt1.X-Pt2.X, Pt2.Y-Pt3.Y))
1210 else
1211 {$ENDIF}
1212 Result := (Pt1.Y-Pt2.Y)*(Pt2.X-Pt3.X) = (Pt1.X-Pt2.X)*(Pt2.Y-Pt3.Y);
1213 end;
1214 //---------------------------------------------------------------------------
1215
SlopesEqualnull1216 function SlopesEqual(const L1a, L1b, L2a, L2b: TIntPoint;
1217 UseFullInt64Range: Boolean): Boolean; overload;
1218 begin
1219 {$IFNDEF use_int32}
1220 if UseFullInt64Range then
1221 Result := Int128Equal(
1222 Int128Mul(L1a.Y-L1b.Y, L2a.X-L2b.X), Int128Mul(L2a.Y-L2b.Y, L1a.X-L1b.X))
1223 else
1224 {$ENDIF}
1225 //dy1 * dx2 = dy2 * dx1
1226 Result := (L1a.Y-L1b.Y)*(L2a.X-L2b.X) = (L2a.Y-L2b.Y)*(L1a.X-L1b.X);
1227 end;
1228
1229 (*****************************************************************************
1230 * Dx: 0(90�) Slope: 0 = Dx: -inf *
1231 * | Slope: 0.5 = Dx: -2 *
1232 * +inf (180�) <--- o ---> -inf (0�) Slope: 2.0 = Dx: -0.5 *
1233 * Slope: inf = Dx: 0 *
1234 *****************************************************************************)
1235
GetDxnull1236 function GetDx(const Pt1, Pt2: TIntPoint): Double;
1237 begin
1238 if (Pt1.Y = Pt2.Y) then Result := Horizontal
1239 else Result := (Pt2.X - Pt1.X)/(Pt2.Y - Pt1.Y);
1240 end;
1241 //---------------------------------------------------------------------------
1242
1243 procedure SetDx(E: PEdge); {$IFDEF INLINING} inline; {$ENDIF}
1244 var
1245 dy: cInt;
1246 begin
1247 dy := (E.Top.Y - E.Bot.Y);
1248 if dy = 0 then E.Dx := Horizontal
1249 else E.Dx := (E.Top.X - E.Bot.X)/dy;
1250 end;
1251 //---------------------------------------------------------------------------
1252
IsHorizontalnull1253 function IsHorizontal(E: PEdge): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
1254 begin
1255 Result := E.Dx = Horizontal;
1256 end;
1257 //------------------------------------------------------------------------------
1258
1259 procedure Swap(var val1, val2: cInt); {$IFDEF INLINING} inline; {$ENDIF}
1260 var
1261 tmp: cInt;
1262 begin
1263 tmp := val1;
1264 val1 := val2;
1265 val2 := tmp;
1266 end;
1267 //---------------------------------------------------------------------------
1268
1269 procedure SwapSides(Edge1, Edge2: PEdge); {$IFDEF INLINING} inline; {$ENDIF}
1270 var
1271 Side: TEdgeSide;
1272 begin
1273 Side := Edge1.Side;
1274 Edge1.Side := Edge2.Side;
1275 Edge2.Side := Side;
1276 end;
1277 //------------------------------------------------------------------------------
1278
1279 procedure SwapPolyIndexes(Edge1, Edge2: PEdge);
1280 {$IFDEF INLINING} inline; {$ENDIF}
1281 var
1282 OutIdx: Integer;
1283 begin
1284 OutIdx := Edge1.OutIdx;
1285 Edge1.OutIdx := Edge2.OutIdx;
1286 Edge2.OutIdx := OutIdx;
1287 end;
1288 //------------------------------------------------------------------------------
1289
NextIsHorznull1290 function NextIsHorz(Edge: PEdge): Boolean;
1291 {$IFDEF INLINING} inline; {$ENDIF}
1292 begin
1293 Result := assigned(Edge.NextInLML) and (Edge.NextInLML.Dx = Horizontal);
1294 end;
1295 //------------------------------------------------------------------------------
1296
NextIsHorzAtnull1297 function NextIsHorzAt(Edge: PEdge; Y: cInt): Boolean;
1298 {$IFDEF INLINING} inline; {$ENDIF}
1299 begin
1300 Result := (Edge.Top.Y = Y) and assigned(Edge.NextInLML) and
1301 (Edge.NextInLML.Dx = Horizontal);
1302 end;
1303 //------------------------------------------------------------------------------
1304
TopXnull1305 function TopX(Edge: PEdge; const currentY: cInt): cInt;
1306 {$IFDEF INLINING} inline; {$ENDIF}
1307 begin
1308 if currentY = Edge.Top.Y then Result := Edge.Top.X
1309 else if Edge.Top.X = Edge.Bot.X then Result := Edge.Bot.X
1310 else Result := Edge.Bot.X + Round(Edge.Dx*(currentY - Edge.Bot.Y));
1311 end;
1312 //------------------------------------------------------------------------------
1313
1314 {$IFDEF use_xyz}
1315 Procedure SetZ(var Pt: TIntPoint; E1, E2: PEdge; ZFillFunc: TZFillCallback);
1316 begin
1317 if (Pt.Z <> 0) or not assigned(ZFillFunc) then Exit
1318 else if PointsEqual(Pt, E1.Bot) then Pt.Z := E1.Bot.Z
1319 else if PointsEqual(Pt, E2.Bot) then Pt.Z := E2.Bot.Z
1320 else if PointsEqual(Pt, E1.Top) then Pt.Z := E1.Top.Z
1321 else if PointsEqual(Pt, E2.Top) then Pt.Z := E2.Top.Z
1322 else ZFillFunc(E1.Bot, E1.Top, E2.Bot, E2.Top, Pt);
1323 end;
1324 //------------------------------------------------------------------------------
1325 {$ENDIF}
1326
1327 procedure IntersectPointEx(Edge1, Edge2: PEdge; out ip: TIntPoint);
1328 var
1329 B1,B2,M: Double;
1330 begin
1331 {$IFDEF use_xyz}
1332 ip.Z := 0;
1333 {$ENDIF}
1334 if (edge1.Dx = edge2.Dx) then
1335 begin
1336 ip.Y := edge1.Curr.Y;
1337 ip.X := TopX(edge1, ip.Y);
1338 Exit;
1339 end;
1340
1341 if Edge1.Dx = 0 then
1342 begin
1343 ip.X := Edge1.Bot.X;
1344 if Edge2.Dx = Horizontal then
1345 ip.Y := Edge2.Bot.Y
1346 else
1347 begin
1348 with Edge2^ do B2 := Bot.Y - (Bot.X/Dx);
1349 ip.Y := round(ip.X/Edge2.Dx + B2);
1350 end;
1351 end
1352 else if Edge2.Dx = 0 then
1353 begin
1354 ip.X := Edge2.Bot.X;
1355 if Edge1.Dx = Horizontal then
1356 ip.Y := Edge1.Bot.Y
1357 else
1358 begin
1359 with Edge1^ do B1 := Bot.Y - (Bot.X/Dx);
1360 ip.Y := round(ip.X/Edge1.Dx + B1);
1361 end;
1362 end else
1363 begin
1364 with Edge1^ do B1 := Bot.X - Bot.Y * Dx;
1365 with Edge2^ do B2 := Bot.X - Bot.Y * Dx;
1366 M := (B2-B1)/(Edge1.Dx - Edge2.Dx);
1367 ip.Y := round(M);
1368 if Abs(Edge1.Dx) < Abs(Edge2.Dx) then
1369 ip.X := round(Edge1.Dx * M + B1)
1370 else
1371 ip.X := round(Edge2.Dx * M + B2);
1372 end;
1373
1374 //The precondition - E.Curr.X > eNext.Curr.X - indicates that the two edges do
1375 //intersect below TopY (and hence below the tops of either Edge). However,
1376 //when edges are almost parallel, rounding errors may cause False positives -
1377 //indicating intersections when there really aren't any. Also, floating point
1378 //imprecision can incorrectly place an intersect point beyond/above an Edge.
1379 //Therfore, further adjustment to IP is warranted ...
1380 if (ip.Y < Edge1.Top.Y) or (ip.Y < Edge2.Top.Y) then
1381 begin
1382 //Find the lower top of the two edges and compare X's at this Y.
1383 //If Edge1's X is greater than Edge2's X then it's fair to assume an
1384 //intersection really has occurred...
1385 if (Edge1.Top.Y > Edge2.Top.Y) then
1386 ip.Y := edge1.Top.Y else
1387 ip.Y := edge2.Top.Y;
1388 if Abs(Edge1.Dx) < Abs(Edge2.Dx) then
1389 ip.X := TopX(Edge1, ip.Y) else
1390 ip.X := TopX(Edge2, ip.Y);
1391 end;
1392 //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ...
1393 if (ip.Y > Edge1.Curr.Y) then
1394 begin
1395 ip.Y := Edge1.Curr.Y;
1396 if (abs(Edge1.Dx) > abs(Edge2.Dx)) then //ie use more vertical edge
1397 ip.X := TopX(Edge2, ip.Y) else
1398 ip.X := TopX(Edge1, ip.Y);
1399 end;
1400 end;
1401 //------------------------------------------------------------------------------
1402
1403 procedure ReversePolyPtLinks(PP: POutPt);
1404 var
1405 Pp1,Pp2: POutPt;
1406 begin
1407 if not Assigned(PP) then Exit;
1408 Pp1 := PP;
1409 repeat
1410 Pp2:= Pp1.Next;
1411 Pp1.Next := Pp1.Prev;
1412 Pp1.Prev := Pp2;
1413 Pp1 := Pp2;
1414 until Pp1 = PP;
1415 end;
1416 //------------------------------------------------------------------------------
1417
Pt2IsBetweenPt1AndPt3null1418 function Pt2IsBetweenPt1AndPt3(const Pt1, Pt2, Pt3: TIntPoint): Boolean;
1419 begin
1420 //nb: assumes collinearity.
1421 if PointsEqual(Pt1, Pt3) or PointsEqual(Pt1, Pt2) or PointsEqual(Pt3, Pt2) then
1422 Result := False
1423 else if (Pt1.X <> Pt3.X) then
1424 Result := (Pt2.X > Pt1.X) = (Pt2.X < Pt3.X)
1425 else
1426 Result := (Pt2.Y > Pt1.Y) = (Pt2.Y < Pt3.Y);
1427 end;
1428 //------------------------------------------------------------------------------
1429
GetOverlapnull1430 function GetOverlap(const A1, A2, B1, B2: cInt; out Left, Right: cInt): Boolean;
1431 begin
1432 if (A1 < A2) then
1433 begin
1434 if (B1 < B2) then begin Left := Max(A1,B1); Right := Min(A2,B2); end
1435 else begin Left := Max(A1,B2); Right := Min(A2,B1); end;
1436 end else
1437 begin
1438 if (B1 < B2) then begin Left := Max(A2,B1); Right := Min(A1,B2); end
1439 else begin Left := Max(A2,B2); Right := Min(A1,B1); end
1440 end;
1441 Result := Left < Right;
1442 end;
1443 //------------------------------------------------------------------------------
1444
1445 procedure UpdateOutPtIdxs(OutRec: POutRec);
1446 var
1447 op: POutPt;
1448 begin
1449 op := OutRec.Pts;
1450 repeat
1451 op.Idx := OutRec.Idx;
1452 op := op.Prev;
1453 until op = OutRec.Pts;
1454 end;
1455 //------------------------------------------------------------------------------
1456
1457 procedure RangeTest(const Pt: TIntPoint; var Use64BitRange: Boolean);
1458 begin
1459 if Use64BitRange then
1460 begin
1461 if (Pt.X > HiRange) or (Pt.Y > HiRange) or
1462 (-Pt.X > HiRange) or (-Pt.Y > HiRange) then
1463 raise exception.Create(rsInvalidInt);
1464 end
1465 else if (Pt.X > LoRange) or (Pt.Y > LoRange) or
1466 (-Pt.X > LoRange) or (-Pt.Y > LoRange) then
1467 begin
1468 Use64BitRange := true;
1469 RangeTest(Pt, Use64BitRange);
1470 end;
1471 end;
1472 //------------------------------------------------------------------------------
1473
1474 procedure ReverseHorizontal(E: PEdge);
1475 begin
1476 //swap horizontal edges' top and bottom x's so they follow the natural
1477 //progression of the bounds - ie so their xbots will align with the
1478 //adjoining lower Edge. [Helpful in the ProcessHorizontal() method.]
1479 Swap(E.Top.X, E.Bot.X);
1480 {$IFDEF use_xyz}
1481 Swap(E.Top.Z, E.Bot.Z);
1482 {$ENDIF}
1483 end;
1484 //------------------------------------------------------------------------------
1485
1486 procedure InitEdge(E, Next, Prev: PEdge;
1487 const Pt: TIntPoint); {$IFDEF INLINING} inline; {$ENDIF}
1488 begin
1489 E.Curr := Pt;
1490 E.Next := Next;
1491 E.Prev := Prev;
1492 E.OutIdx := Unassigned;
1493 end;
1494 //------------------------------------------------------------------------------
1495
1496 procedure InitEdge2(E: PEdge; PolyType: TPolyType);
1497 {$IFDEF INLINING} inline; {$ENDIF}
1498 begin
1499 if E.Curr.Y >= E.Next.Curr.Y then
1500 begin
1501 E.Bot := E.Curr;
1502 E.Top := E.Next.Curr;
1503 end else
1504 begin
1505 E.Top := E.Curr;
1506 E.Bot := E.Next.Curr;
1507 end;
1508 SetDx(E);
1509 E.PolyType := PolyType;
1510 end;
1511 //------------------------------------------------------------------------------
1512
RemoveEdgenull1513 function RemoveEdge(E: PEdge): PEdge; {$IFDEF INLINING} inline; {$ENDIF}
1514 begin
1515 //removes E from double_linked_list (but without disposing from memory)
1516 E.Prev.Next := E.Next;
1517 E.Next.Prev := E.Prev;
1518 Result := E.Next;
1519 E.Prev := nil; //flag as removed (see ClipperBase.Clear)
1520 end;
1521 //------------------------------------------------------------------------------
1522
FindNextLocMinnull1523 function FindNextLocMin(E: PEdge): PEdge; {$IFDEF INLINING} inline; {$ENDIF}
1524 var
1525 E2: PEdge;
1526 begin
1527 while True do
1528 begin
1529 while not PointsEqual(E.Bot, E.Prev.Bot) or
1530 PointsEqual(E.Curr, E.Top) do E := E.Next;
1531 if (E.Dx <> Horizontal) and (E.Prev.Dx <> Horizontal) then break;
1532 while (E.Prev.Dx = Horizontal) do E := E.Prev;
1533 E2 := E; //E2 == first horizontal
1534 while (E.Dx = Horizontal) do E := E.Next;
1535 if (E.Top.Y = E.Prev.Bot.Y) then Continue; //ie just an intermediate horz.
1536 //E == first edge past horizontals
1537 if E2.Prev.Bot.X < E.Bot.X then E := E2;
1538 //E is first horizontal when CW and first past horizontals when CCW
1539 break;
1540 end;
1541 Result := E;
1542 end;
1543 //------------------------------------------------------------------------------
1544
GetUnitNormalnull1545 function GetUnitNormal(const Pt1, Pt2: TIntPoint): TDoublePoint;
1546 var
1547 Dx, Dy, F: Double;
1548 begin
1549 if (Pt2.X = Pt1.X) and (Pt2.Y = Pt1.Y) then
1550 begin
1551 Result.X := 0;
1552 Result.Y := 0;
1553 Exit;
1554 end;
1555
1556 Dx := (Pt2.X - Pt1.X);
1557 Dy := (Pt2.Y - Pt1.Y);
1558 F := 1 / Hypot(Dx, Dy);
1559 Dx := Dx * F;
1560 Dy := Dy * F;
1561 Result.X := Dy;
1562 Result.Y := -Dx
1563 end;
1564
1565 //------------------------------------------------------------------------------
1566 // TClipperBase methods ...
1567 //------------------------------------------------------------------------------
1568
1569 constructor TClipperBase.Create;
1570 begin
1571 inherited;
1572 FEdgeList := TEgdeList.Create;
1573 FLocMinList := TLocMinList.Create;
1574 FPolyOutList := TPolyOutList.Create;
1575 FCurrentLocMinIdx := 0;
1576 FUse64BitRange := False; //ie default is False
1577 end;
1578 //------------------------------------------------------------------------------
1579
1580 destructor TClipperBase.Destroy;
1581 begin
1582 Clear;
1583 DisposeScanbeamList;
1584 FPolyOutList.Free;
1585 FEdgeList.Free;
1586 FLocMinList.Free;
1587 inherited;
1588 end;
1589 //------------------------------------------------------------------------------
1590
TClipperBase.ProcessBoundnull1591 function TClipperBase.ProcessBound(E: PEdge; NextIsForward: Boolean): PEdge;
1592 var
1593 EStart, Horz: PEdge;
1594 locMin: PLocalMinimum;
1595 begin
1596 Result := E;
1597 if (E.OutIdx = Skip) then
1598 begin
1599 //check if there are edges beyond the skip edge in the bound and if so
1600 //create another LocMin and calling ProcessBound once more ...
1601 if NextIsForward then
1602 begin
1603 while (E.Top.Y = E.Next.Bot.Y) do
1604 E := E.Next;
1605 //don't include top horizontals here ...
1606 while (E <> Result) and (E.Dx = Horizontal) do E := E.Prev;
1607 end else
1608 begin
1609 while (E.Top.Y = E.Prev.Bot.Y) do E := E.Prev;
1610 while (E <> Result) and (E.Dx = Horizontal) do E := E.Next;
1611 end;
1612 if E = Result then
1613 begin
1614 if NextIsForward then Result := E.Next
1615 else Result := E.Prev;
1616 end else
1617 begin
1618 if NextIsForward then
1619 E := Result.Next else
1620 E := Result.Prev;
1621 new(locMin);
1622 locMin.Y := E.Bot.Y;
1623 locMin.LeftBound := nil;
1624 locMin.RightBound := E;
1625 E.WindDelta := 0;
1626 Result := ProcessBound(E, NextIsForward);
1627 FLocMinList.Add(locMin);
1628 end;
1629 Exit;
1630 end;
1631
1632 if (E.Dx = Horizontal) then
1633 begin
1634 //We need to be careful with open paths because this may not be a
1635 //true local minima (ie E may be following a skip edge).
1636 //Also, consecutive horz. edges may start heading left before going right.
1637 if NextIsForward then EStart := E.Prev
1638 else EStart := E.Next;
1639 if (EStart.Dx = Horizontal) then //ie an adjoining horizontal skip edge
1640 begin
1641 if (EStart.Bot.X <> E.Bot.X) and (EStart.Top.X <> E.Bot.X) then
1642 ReverseHorizontal(E);
1643 end
1644 else if (EStart.Bot.X <> E.Bot.X) then
1645 ReverseHorizontal(E);
1646 end;
1647
1648 EStart := E;
1649 if NextIsForward then
1650 begin
1651 while (Result.Top.Y = Result.Next.Bot.Y) and (Result.Next.OutIdx <> Skip) do
1652 Result := Result.Next;
1653 if (Result.Dx = Horizontal) and (Result.Next.OutIdx <> Skip) then
1654 begin
1655 //nb: at the top of a bound, horizontals are added to the bound
1656 //only when the preceding edge attaches to the horizontal's left vertex
1657 //unless a Skip edge is encountered when that becomes the top divide
1658 Horz := Result;
1659 while (Horz.Prev.Dx = Horizontal) do Horz := Horz.Prev;
1660 if (Horz.Prev.Top.X > Result.Next.Top.X) then Result := Horz.Prev;
1661 end;
1662 while (E <> Result) do
1663 begin
1664 e.NextInLML := e.Next;
1665 if (E.Dx = Horizontal) and (e <> EStart) and
1666 (E.Bot.X <> E.Prev.Top.X) then ReverseHorizontal(E);
1667 E := E.Next;
1668 end;
1669 if (e <> EStart) and (E.Dx = Horizontal) and (E.Bot.X <> E.Prev.Top.X) then
1670 ReverseHorizontal(E);
1671 Result := Result.Next; //move to the edge just beyond current bound
1672 end else
1673 begin
1674 while (Result.Top.Y = Result.Prev.Bot.Y) and (Result.Prev.OutIdx <> Skip) do
1675 Result := Result.Prev;
1676 if (Result.Dx = Horizontal) and (Result.Prev.OutIdx <> Skip) then
1677 begin
1678 Horz := Result;
1679 while (Horz.Next.Dx = Horizontal) do Horz := Horz.Next;
1680 if (Horz.Next.Top.X = Result.Prev.Top.X) or
1681 (Horz.Next.Top.X > Result.Prev.Top.X) then Result := Horz.Next;
1682 end;
1683 while (E <> Result) do
1684 begin
1685 e.NextInLML := e.Prev;
1686 if (e.Dx = Horizontal) and (e <> EStart) and
1687 (E.Bot.X <> E.Next.Top.X) then ReverseHorizontal(E);
1688 E := E.Prev;
1689 end;
1690 if (e <> EStart) and (E.Dx = Horizontal) and (E.Bot.X <> E.Next.Top.X) then
1691 ReverseHorizontal(E);
1692 Result := Result.Prev; //move to the edge just beyond current bound
1693 end;
1694 end;
1695 //------------------------------------------------------------------------------
1696
AddPathnull1697 function TClipperBase.AddPath(const Path: TPath;
1698 PolyType: TPolyType; Closed: Boolean): Boolean;
1699 var
1700 I, HighI: Integer;
1701 Edges: PEdgeArray;
1702 E, E2, EMin, EStart, ELoopStop: PEdge;
1703 IsFlat, leftBoundIsForward: Boolean;
1704 locMin: PLocalMinimum;
1705 begin
1706 {$IFDEF use_lines}
1707 if not Closed and (polyType = ptClip) then
1708 raise exception.Create(rsOpenPath);
1709 {$ELSE}
1710 if not Closed then raise exception.Create(rsOpenPath2);
1711 {$ENDIF}
1712
1713 Result := false;
1714 IsFlat := true;
1715
1716 //1. Basic (first) edge initialization ...
1717 HighI := High(Path);
1718 if Closed then
1719 while (HighI > 0) and PointsEqual(Path[HighI],Path[0]) do Dec(HighI);
1720 while (HighI > 0) and PointsEqual(Path[HighI],Path[HighI -1]) do Dec(HighI);
1721 if (Closed and (HighI < 2)) or (not Closed and (HighI < 1)) then Exit;
1722
1723 GetMem(Edges, sizeof(TEdge)*(HighI +1));
1724 try
1725 FillChar(Edges^, sizeof(TEdge)*(HighI +1), 0);
1726 Edges[1].Curr := Path[1];
1727 RangeTest(Path[0], FUse64BitRange);
1728 RangeTest(Path[HighI], FUse64BitRange);
1729 InitEdge(@Edges[0], @Edges[1], @Edges[HighI], Path[0]);
1730 InitEdge(@Edges[HighI], @Edges[0], @Edges[HighI-1], Path[HighI]);
1731 for I := HighI - 1 downto 1 do
1732 begin
1733 RangeTest(Path[I], FUse64BitRange);
1734 InitEdge(@Edges[I], @Edges[I+1], @Edges[I-1], Path[I]);
1735 end;
1736 except
1737 FreeMem(Edges);
1738 raise; //Range test fails
1739 end;
1740 EStart := @Edges[0];
1741
1742 //2. Remove duplicate vertices, and (when closed) collinear edges ...
1743 E := EStart;
1744 ELoopStop := EStart;
1745 while (E <> E.Next) do //ie in case loop reduces to a single vertex
1746 begin
1747 //allow matching start and end points when not Closed ...
1748 if PointsEqual(E.Curr, E.Next.Curr) and
1749 (Closed or (E.Next <> EStart)) then
1750 begin
1751 if E = EStart then EStart := E.Next;
1752 E := RemoveEdge(E);
1753 ELoopStop := E;
1754 Continue;
1755 end;
1756 if (E.Prev = E.Next) then
1757 Break //only two vertices
1758 else if Closed and
1759 SlopesEqual(E.Prev.Curr, E.Curr, E.Next.Curr, FUse64BitRange) and
1760 (not FPreserveCollinear or
1761 not Pt2IsBetweenPt1AndPt3(E.Prev.Curr, E.Curr, E.Next.Curr)) then
1762 begin
1763 //Collinear edges are allowed for open paths but in closed paths
1764 //the default is to merge adjacent collinear edges into a single edge.
1765 //However, if the PreserveCollinear property is enabled, only overlapping
1766 //collinear edges (ie spikes) will be removed from closed paths.
1767 if E = EStart then EStart := E.Next;
1768 E := RemoveEdge(E);
1769 E := E.Prev;
1770 ELoopStop := E;
1771 Continue;
1772 end;
1773 E := E.Next;
1774 //todo - manage open paths which start and end at same point
1775 if (E = eLoopStop) then Break;
1776 if E = ELoopStop then Break;
1777 end;
1778
1779 if (not Closed and (E = E.Next)) or (Closed and (E.Prev = E.Next)) then
1780 begin
1781 FreeMem(Edges);
1782 Exit;
1783 end;
1784
1785 if not Closed then
1786 begin
1787 FHasOpenPaths := true;
1788 EStart.Prev.OutIdx := Skip;
1789 end;
1790
1791 //3. Do second stage of edge initialization ...
1792 E := EStart;
1793 repeat
1794 InitEdge2(E, polyType);
1795 E := E.Next;
1796 if IsFlat and (E.Curr.Y <> EStart.Curr.Y) then IsFlat := false;
1797 until E = EStart;
1798 //4. Finally, add edge bounds to LocalMinima list ...
1799
1800 //Totally flat paths must be handled differently when adding them
1801 //to LocalMinima list to avoid endless loops etc ...
1802 if (IsFlat) then
1803 begin
1804 if Closed then
1805 begin
1806 FreeMem(Edges);
1807 Exit;
1808 end;
1809 new(locMin);
1810 locMin.Y := E.Bot.Y;
1811 locMin.LeftBound := nil;
1812 locMin.RightBound := E;
1813 locMin.RightBound.Side := esRight;
1814 locMin.RightBound.WindDelta := 0;
1815 while true do
1816 begin
1817 if E.Bot.X <> E.Prev.Top.X then ReverseHorizontal(E);
1818 if E.Next.OutIdx = Skip then break;
1819 E.NextInLML := E.Next;
1820 E := E.Next;
1821 end;
1822 FLocMinList.Add(locMin);
1823 Result := true;
1824 FEdgeList.Add(Edges);
1825 Exit;
1826 end;
1827
1828 Result := true;
1829 FEdgeList.Add(Edges);
1830 EMin := nil;
1831
1832 //workaround to avoid an endless loop in the while loop below when
1833 //open paths have matching start and end points ...
1834 if PointsEqual(E.Prev.Bot, E.Prev.Top) then E := E.Next;
1835
1836 while true do
1837 begin
1838 E := FindNextLocMin(E);
1839 if (E = EMin) then break
1840 else if (EMin = nil) then EMin := E;
1841
1842 //E and E.Prev now share a local minima (left aligned if horizontal).
1843 //Compare their slopes to find which starts which bound ...
1844 new(locMin);
1845 locMin.Y := E.Bot.Y;
1846 if (E.Dx < E.Prev.Dx) then
1847 begin
1848 locMin.LeftBound := E.Prev;
1849 locMin.RightBound := E; //can be horz when CW
1850 leftBoundIsForward := false; //Q.nextInLML = Q.prev
1851 end else
1852 begin
1853 locMin.LeftBound := E;
1854 locMin.RightBound := E.Prev; //can be horz when CCW
1855 leftBoundIsForward := true; //Q.nextInLML = Q.next
1856 end;
1857
1858 if not Closed then locMin.LeftBound.WindDelta := 0
1859 else if (locMin.LeftBound.Next = locMin.RightBound) then
1860 locMin.LeftBound.WindDelta := -1
1861 else locMin.LeftBound.WindDelta := 1;
1862 locMin.RightBound.WindDelta := -locMin.LeftBound.WindDelta;
1863
1864 E := ProcessBound(locMin.LeftBound, leftBoundIsForward);
1865 if E.OutIdx = Skip then E := ProcessBound(E, leftBoundIsForward);
1866
1867 E2 := ProcessBound(locMin.RightBound, not leftBoundIsForward);
1868 if E2.OutIdx = Skip then E2 := ProcessBound(E2, not leftBoundIsForward);
1869
1870 if (locMin.LeftBound.OutIdx = Skip) then locMin.LeftBound := nil
1871 else if (locMin.RightBound.OutIdx = Skip) then locMin.RightBound := nil;
1872 FLocMinList.Add(locMin);
1873
1874 if not leftBoundIsForward then E := E2;
1875 end;
1876 end;
1877 //------------------------------------------------------------------------------
1878
AddPathsnull1879 function TClipperBase.AddPaths(const Paths: TPaths;
1880 PolyType: TPolyType; Closed: Boolean): Boolean;
1881 var
1882 I: Integer;
1883 begin
1884 Result := False;
1885 for I := 0 to high(Paths) do
1886 if AddPath(Paths[I], PolyType, Closed) then Result := True;
1887 end;
1888 //------------------------------------------------------------------------------
1889
1890 procedure TClipperBase.Clear;
1891 var
1892 I: Integer;
1893 begin
1894 DisposeLocalMinimaList;
1895 //dispose of Edges ...
1896 for I := 0 to FEdgeList.Count -1 do
1897 FreeMem(PEdgeArray(fEdgeList[I]));
1898 FEdgeList.Clear;
1899
1900 FUse64BitRange := False;
1901 FHasOpenPaths := False;
1902 end;
1903 //------------------------------------------------------------------------------
1904
1905 procedure TClipperBase.InsertScanbeam(const Y: cInt);
1906 var
1907 newSb, sb: PScanbeam;
1908 begin
1909 //single-linked list: sorted descending, ignoring dups.
1910 new(newSb);
1911 newSb.Y := Y;
1912 if not Assigned(fScanbeam) then
1913 begin
1914 FScanbeam := newSb;
1915 newSb.Next := nil;
1916 end else if Y > FScanbeam.Y then
1917 begin
1918 newSb.Next := FScanbeam;
1919 FScanbeam := newSb;
1920 end else
1921 begin
1922 sb := FScanbeam;
1923 while Assigned(sb.Next) and (Y <= sb.Next.Y) do sb := sb.Next;
1924 if Y <> sb.Y then
1925 begin
1926 newSb.Next := sb.Next;
1927 sb.Next := newSb;
1928 end
1929 else dispose(newSb);
1930 end;
1931 end;
1932 //------------------------------------------------------------------------------
1933
PopScanbeamnull1934 function TClipperBase.PopScanbeam(out Y: cInt): Boolean;
1935 var
1936 Sb: PScanbeam;
1937 begin
1938 Result := assigned(FScanbeam);
1939 if not result then exit;
1940 Y := FScanbeam.Y;
1941 Sb := FScanbeam;
1942 FScanbeam := FScanbeam.Next;
1943 dispose(Sb);
1944 end;
1945 //------------------------------------------------------------------------------
1946
TClipperBase.LocalMinimaPendingnull1947 function TClipperBase.LocalMinimaPending: Boolean;
1948 begin
1949 Result := FCurrentLocMinIdx < FLocMinList.Count;
1950 end;
1951 //------------------------------------------------------------------------------
1952
TClipperBase.PopLocalMinimanull1953 function TClipperBase.PopLocalMinima(Y: cInt;
1954 out LocalMinima: PLocalMinimum): Boolean;
1955 begin
1956 Result := false;
1957 if (FCurrentLocMinIdx = FLocMinList.Count) then Exit;
1958 LocalMinima := PLocalMinimum(FLocMinList[FCurrentLocMinIdx]);
1959 if (LocalMinima.Y = Y) then
1960 begin
1961 inc(FCurrentLocMinIdx);
1962 Result := true;
1963 end;
1964 end;
1965 //------------------------------------------------------------------------------
1966
1967 procedure TClipperBase.DisposeScanbeamList;
1968 var
1969 SB: PScanbeam;
1970 begin
1971 while Assigned(fScanbeam) do
1972 begin
1973 SB := FScanbeam.Next;
1974 Dispose(fScanbeam);
1975 FScanbeam := SB;
1976 end;
1977 end;
1978 //------------------------------------------------------------------------------
1979
1980 {$IFNDEF USEGENERICS}
LocMinListSortnull1981 function LocMinListSort(item1, item2:Pointer): Integer;
1982 var
1983 y: cInt;
1984 begin
1985 y := PLocalMinimum(item2).Y - PLocalMinimum(item1).Y;
1986 if y < 0 then result := -1
1987 else if y > 0 then result := 1
1988 else result := 0;
1989 end;
1990 {$ENDIF}
1991
1992 //------------------------------------------------------------------------------
1993
1994 procedure TClipperBase.Reset;
1995 var
1996 i: Integer;
1997 Lm: PLocalMinimum;
1998 begin
1999 //Reset() allows various clipping operations to be executed
2000 //multiple times on the same polygon sets.
2001 {$IFDEF USEGENERICS}
2002 FLocMinList.Sort(TComparer<PLocalMinimum>.Construct(
2003 function (const Item1, Item2 : PLocalMinimum) : integer
2004 var
2005 y: cInt;
2006 begin
2007 y := PLocalMinimum(item2).Y - PLocalMinimum(item1).Y;
2008 if y < 0 then result := -1
2009 else if y > 0 then result := 1
2010 else result := 0;
2011 end
2012 ));
2013 {$ELSE}
2014 FLocMinList.Sort(LocMinListSort);
2015 {$ENDIF}
2016 for i := 0 to FLocMinList.Count -1 do
2017 begin
2018 Lm := PLocalMinimum(FLocMinList[i]);
2019 InsertScanbeam(Lm.Y);
2020 //resets just the two (L & R) edges attached to each Local Minima ...
2021 if assigned(Lm.LeftBound) then
2022 with Lm.LeftBound^ do
2023 begin
2024 Curr := Bot;
2025 OutIdx := Unassigned;
2026 end;
2027 if assigned(Lm.RightBound) then
2028 with Lm.RightBound^ do
2029 begin
2030 Curr := Bot;
2031 OutIdx := Unassigned;
2032 end;
2033 end;
2034 FCurrentLocMinIdx := 0;
2035 FActiveEdges := nil;
2036 end;
2037 //------------------------------------------------------------------------------
2038
2039 procedure TClipperBase.DisposePolyPts(PP: POutPt);
2040 var
2041 TmpPp: POutPt;
2042 begin
2043 PP.Prev.Next := nil;
2044 while Assigned(PP) do
2045 begin
2046 TmpPp := PP;
2047 PP := PP.Next;
2048 dispose(TmpPp);
2049 end;
2050 end;
2051 //------------------------------------------------------------------------------
2052
2053 procedure TClipperBase.DisposeLocalMinimaList;
2054 var
2055 i: Integer;
2056 begin
2057 for i := 0 to FLocMinList.Count -1 do
2058 Dispose(PLocalMinimum(FLocMinList[i]));
2059 FLocMinList.Clear;
2060 FCurrentLocMinIdx := 0;
2061 end;
2062 //------------------------------------------------------------------------------
2063
CreateOutRecnull2064 function TClipperBase.CreateOutRec: POutRec;
2065 begin
2066 new(Result);
2067 Result.IsHole := False;
2068 Result.IsOpen := False;
2069 Result.FirstLeft := nil;
2070 Result.Pts := nil;
2071 Result.BottomPt := nil;
2072 Result.PolyNode := nil;
2073 Result.Idx := FPolyOutList.Add(Result);
2074 end;
2075 //------------------------------------------------------------------------------
2076
2077 procedure TClipperBase.DisposeOutRec(Index: Integer);
2078 var
2079 OutRec: POutRec;
2080 begin
2081 OutRec := FPolyOutList[Index];
2082 if Assigned(OutRec.Pts) then DisposePolyPts(OutRec.Pts);
2083 Dispose(OutRec);
2084 FPolyOutList[Index] := nil;
2085 end;
2086 //------------------------------------------------------------------------------
2087
2088 procedure TClipperBase.DisposeAllOutRecs;
2089 var
2090 I: Integer;
2091 begin
2092 for I := 0 to FPolyOutList.Count -1 do DisposeOutRec(I);
2093 FPolyOutList.Clear;
2094 end;
2095 //------------------------------------------------------------------------------
2096
2097 procedure TClipperBase.UpdateEdgeIntoAEL(var E: PEdge);
2098 var
2099 AelPrev, AelNext: PEdge;
2100 begin
2101 //return true when AddOutPt() call needed too
2102 if not Assigned(E.NextInLML) then
2103 raise exception.Create(rsUpdateEdgeIntoAEL);
2104
2105 E.NextInLML.OutIdx := E.OutIdx;
2106
2107 AelPrev := E.PrevInAEL;
2108 AelNext := E.NextInAEL;
2109 if Assigned(AelPrev) then
2110 AelPrev.NextInAEL := E.NextInLML else
2111 FActiveEdges := E.NextInLML;
2112 if Assigned(AelNext) then
2113 AelNext.PrevInAEL := E.NextInLML;
2114 E.NextInLML.Side := E.Side;
2115 E.NextInLML.WindDelta := E.WindDelta;
2116 E.NextInLML.WindCnt := E.WindCnt;
2117 E.NextInLML.WindCnt2 := E.WindCnt2;
2118 E := E.NextInLML; ////
2119 E.Curr := E.Bot;
2120 E.PrevInAEL := AelPrev;
2121 E.NextInAEL := AelNext;
2122 if E.Dx <> Horizontal then
2123 InsertScanbeam(E.Top.Y);
2124 end;
2125 //------------------------------------------------------------------------------
2126
2127 procedure TClipperBase.SwapPositionsInAEL(E1, E2: PEdge);
2128 var
2129 Prev,Next: PEdge;
2130 begin
2131 //check that one or other edge hasn't already been removed from AEL ...
2132 if (E1.NextInAEL = E1.PrevInAEL) or (E2.NextInAEL = E2.PrevInAEL) then
2133 Exit;
2134
2135 if E1.NextInAEL = E2 then
2136 begin
2137 Next := E2.NextInAEL;
2138 if Assigned(Next) then Next.PrevInAEL := E1;
2139 Prev := E1.PrevInAEL;
2140 if Assigned(Prev) then Prev.NextInAEL := E2;
2141 E2.PrevInAEL := Prev;
2142 E2.NextInAEL := E1;
2143 E1.PrevInAEL := E2;
2144 E1.NextInAEL := Next;
2145 end
2146 else if E2.NextInAEL = E1 then
2147 begin
2148 Next := E1.NextInAEL;
2149 if Assigned(Next) then Next.PrevInAEL := E2;
2150 Prev := E2.PrevInAEL;
2151 if Assigned(Prev) then Prev.NextInAEL := E1;
2152 E1.PrevInAEL := Prev;
2153 E1.NextInAEL := E2;
2154 E2.PrevInAEL := E1;
2155 E2.NextInAEL := Next;
2156 end else
2157 begin
2158 Next := E1.NextInAEL;
2159 Prev := E1.PrevInAEL;
2160 E1.NextInAEL := E2.NextInAEL;
2161 if Assigned(E1.NextInAEL) then E1.NextInAEL.PrevInAEL := E1;
2162 E1.PrevInAEL := E2.PrevInAEL;
2163 if Assigned(E1.PrevInAEL) then E1.PrevInAEL.NextInAEL := E1;
2164 E2.NextInAEL := Next;
2165 if Assigned(E2.NextInAEL) then E2.NextInAEL.PrevInAEL := E2;
2166 E2.PrevInAEL := Prev;
2167 if Assigned(E2.PrevInAEL) then E2.PrevInAEL.NextInAEL := E2;
2168 end;
2169 if not Assigned(E1.PrevInAEL) then FActiveEdges := E1
2170 else if not Assigned(E2.PrevInAEL) then FActiveEdges := E2;
2171 end;
2172 //------------------------------------------------------------------------------
2173
2174 procedure TClipperBase.DeleteFromAEL(E: PEdge);
2175 var
2176 AelPrev, AelNext: PEdge;
2177 begin
2178 AelPrev := E.PrevInAEL;
2179 AelNext := E.NextInAEL;
2180 if not Assigned(AelPrev) and not Assigned(AelNext) and
2181 (E <> FActiveEdges) then Exit; //already deleted
2182 if Assigned(AelPrev) then AelPrev.NextInAEL := AelNext
2183 else FActiveEdges := AelNext;
2184 if Assigned(AelNext) then AelNext.PrevInAEL := AelPrev;
2185 E.NextInAEL := nil;
2186 E.PrevInAEL := nil;
2187 end;
2188
2189 //------------------------------------------------------------------------------
2190 // TClipper methods ...
2191 //------------------------------------------------------------------------------
2192
2193 constructor TClipper.Create(InitOptions: TInitOptions = []);
2194 begin
2195 inherited Create;
2196 FJoinList := TJoinList.Create;
2197 FGhostJoinList := TJoinList.Create;
2198 FIntersectList := TIntersecList.Create;
2199 if ioReverseSolution in InitOptions then
2200 FReverseOutput := true;
2201 if ioStrictlySimple in InitOptions then
2202 FStrictSimple := true;
2203 if ioPreserveCollinear in InitOptions then
2204 FPreserveCollinear := true;
2205 end;
2206 //------------------------------------------------------------------------------
2207
2208 destructor TClipper.Destroy;
2209 begin
2210 inherited; //this must be first since inherited Destroy calls Clear.
2211 FJoinList.Free;
2212 FGhostJoinList.Free;
2213 FIntersectList.Free;
2214 end;
2215 //------------------------------------------------------------------------------
2216
Executenull2217 function TClipper.Execute(clipType: TClipType;
2218 out solution: TPaths;
2219 FillType: TPolyFillType = pftEvenOdd): Boolean;
2220 begin
2221 Result := Execute(clipType, solution, FillType, FillType);
2222 end;
2223 //------------------------------------------------------------------------------
2224
Executenull2225 function TClipper.Execute(clipType: TClipType;
2226 out solution: TPaths;
2227 subjFillType: TPolyFillType; clipFillType: TPolyFillType): Boolean;
2228 begin
2229 Result := False;
2230 solution := nil;
2231 if FExecuteLocked then Exit;
2232 //nb: Open paths can only be returned via the PolyTree structure ...
2233 if HasOpenPaths then raise Exception.Create(rsOpenPath3);
2234 try try
2235 FExecuteLocked := True;
2236 FSubjFillType := subjFillType;
2237 FClipFillType := clipFillType;
2238 FClipType := clipType;
2239 FUsingPolyTree := False;
2240 Result := ExecuteInternal;
2241 if Result then
2242 solution := BuildResult;
2243 except
2244 solution := nil;
2245 Result := False;
2246 end;
2247 finally
2248 DisposeAllOutRecs;
2249 FExecuteLocked := False;
2250 end;
2251 end;
2252 //------------------------------------------------------------------------------
2253
Executenull2254 function TClipper.Execute(clipType: TClipType;
2255 out PolyTree: TPolyTree;
2256 FillType: TPolyFillType = pftEvenOdd): Boolean;
2257 begin
2258 Result := Execute(clipType, PolyTree, FillType, FillType);
2259 end;
2260 //------------------------------------------------------------------------------
2261
Executenull2262 function TClipper.Execute(clipType: TClipType;
2263 out PolyTree: TPolyTree;
2264 subjFillType: TPolyFillType;
2265 clipFillType: TPolyFillType): Boolean;
2266 begin
2267 Result := False;
2268 if FExecuteLocked or not Assigned(PolyTree) then Exit;
2269 try try
2270 FExecuteLocked := True;
2271 FSubjFillType := subjFillType;
2272 FClipFillType := clipFillType;
2273 FClipType := clipType;
2274 FUsingPolyTree := True;
2275 Result := ExecuteInternal and BuildResult2(PolyTree);
2276 except
2277 Result := False;
2278 end;
2279 finally
2280 DisposeAllOutRecs;
2281 FExecuteLocked := False;
2282 end;
2283 end;
2284 //------------------------------------------------------------------------------
2285
2286 procedure TClipper.FixHoleLinkage(OutRec: POutRec);
2287 var
2288 orfl: POutRec;
2289 begin
2290 //skip if it's an outermost polygon or if FirstLeft
2291 //already points to the outer/owner polygon ...
2292 if not Assigned(OutRec.FirstLeft) or
2293 ((OutRec.IsHole <> OutRec.FirstLeft.IsHole) and
2294 Assigned(OutRec.FirstLeft.Pts)) then Exit;
2295 orfl := OutRec.FirstLeft;
2296 while Assigned(orfl) and
2297 ((orfl.IsHole = OutRec.IsHole) or not Assigned(orfl.Pts)) do
2298 orfl := orfl.FirstLeft;
2299 OutRec.FirstLeft := orfl;
2300 end;
2301 //------------------------------------------------------------------------------
2302
TClipper.ExecuteInternalnull2303 function TClipper.ExecuteInternal: Boolean;
2304 var
2305 I: Integer;
2306 OutRec: POutRec;
2307 BotY, TopY: cInt;
2308 begin
2309 try
2310 Reset;
2311 FSortedEdges := nil;
2312 Result := false;
2313 if not PopScanbeam(BotY) then Exit;
2314 InsertLocalMinimaIntoAEL(BotY);
2315 while PopScanbeam(TopY) or LocalMinimaPending do
2316 begin
2317 ProcessHorizontals;
2318 ClearGhostJoins;
2319 if not ProcessIntersections(TopY) then Exit;
2320 ProcessEdgesAtTopOfScanbeam(TopY);
2321 BotY := TopY;
2322 InsertLocalMinimaIntoAEL(BotY);
2323 end;
2324
2325 //fix orientations ...
2326 for I := 0 to FPolyOutList.Count -1 do
2327 begin
2328 OutRec := FPolyOutList[I];
2329 if Assigned(OutRec.Pts) and not OutRec.IsOpen and
2330 ((OutRec.IsHole xor FReverseOutput) = (Area(OutRec) > 0)) then
2331 ReversePolyPtLinks(OutRec.Pts);
2332 end;
2333
2334 if FJoinList.count > 0 then JoinCommonEdges;
2335
2336 //unfortunately FixupOutPolygon() must be done after JoinCommonEdges ...
2337 for I := 0 to FPolyOutList.Count -1 do
2338 begin
2339 OutRec := FPolyOutList[I];
2340 if not Assigned(OutRec.Pts) then continue;
2341 if OutRec.IsOpen then
2342 FixupOutPolyline(OutRec)
2343 else
2344 FixupOutPolygon(OutRec);
2345 end;
2346
2347 if FStrictSimple then DoSimplePolygons;
2348 Result := True;
2349 finally
2350 ClearJoins;
2351 ClearGhostJoins;
2352 end;
2353 end;
2354 //------------------------------------------------------------------------------
2355
2356 procedure TClipper.InsertMaxima(const X: cInt);
2357 var
2358 newMax, m: PMaxima;
2359 begin
2360 //double-linked list: sorted ascending, ignoring dups.
2361 new(newMax);
2362 newMax.X := X;
2363 if not Assigned(FMaxima) then
2364 begin
2365 FMaxima := newMax;
2366 newMax.Next := nil;
2367 newMax.Prev := nil;
2368 end else if X < FMaxima.X then
2369 begin
2370 newMax.Next := FMaxima;
2371 newMax.Prev := nil;
2372 FMaxima.Prev := newMax;
2373 FMaxima := newMax;
2374 end else
2375 begin
2376 m := FMaxima;
2377 while Assigned(m.Next) and (X >= m.Next.X) do m := m.Next;
2378 if X <> m.X then
2379 begin
2380 //insert m1 between m2 and m2.Next ...
2381 newMax.Next := m.Next;
2382 newMax.Prev := m;
2383 if assigned(m.Next) then m.Next.Prev := newMax;
2384 m.Next := newMax;
2385 end
2386 else dispose(newMax);
2387 end;
2388 end;
2389 //------------------------------------------------------------------------------
2390
2391 procedure TClipper.DisposeMaximaList;
2392 var
2393 m: PMaxima;
2394 begin
2395 while Assigned(FMaxima) do
2396 begin
2397 m := FMaxima.Next;
2398 Dispose(FMaxima);
2399 FMaxima := m;
2400 end;
2401 end;
2402 //------------------------------------------------------------------------------
2403
2404 procedure TClipper.SetWindingCount(Edge: PEdge);
2405 var
2406 E, E2: PEdge;
2407 Inside: Boolean;
2408 pft: TPolyFillType;
2409 begin
2410 E := Edge.PrevInAEL;
2411 //find the Edge of the same PolyType that immediately preceeds 'Edge' in AEL
2412 while Assigned(E) and ((E.PolyType <> Edge.PolyType) or (E.WindDelta = 0)) do
2413 E := E.PrevInAEL;
2414 if not Assigned(E) then
2415 begin
2416 if Edge.WindDelta = 0 then
2417 begin
2418 if edge.PolyType = ptSubject then
2419 pft := FSubjFillType else
2420 pft := FClipFillType;
2421 if pft = pftNegative then
2422 Edge.WindCnt := -1 else
2423 Edge.WindCnt := 1;
2424 end else Edge.WindCnt := Edge.WindDelta;
2425 Edge.WindCnt2 := 0;
2426 E := FActiveEdges; //ie get ready to calc WindCnt2
2427 end
2428 else if (Edge.WindDelta = 0) and (FClipType <> ctUnion) then
2429 begin
2430 Edge.WindCnt := 1;
2431 Edge.WindCnt2 := E.WindCnt2;
2432 E := E.NextInAEL; //ie get ready to calc WindCnt2
2433 end
2434 else if IsEvenOddFillType(Edge) then
2435 begin
2436 //even-odd filling ...
2437 if (Edge.WindDelta = 0) then //if edge is part of a line
2438 begin
2439 //are we inside a subj polygon ...
2440 Inside := true;
2441 E2 := E.PrevInAEL;
2442 while assigned(E2) do
2443 begin
2444 if (E2.PolyType = E.PolyType) and (E2.WindDelta <> 0) then
2445 Inside := not Inside;
2446 E2 := E2.PrevInAEL;
2447 end;
2448 if Inside then Edge.WindCnt := 0
2449 else Edge.WindCnt := 1;
2450 end
2451 else //else a polygon
2452 begin
2453 Edge.WindCnt := Edge.WindDelta;
2454 end;
2455 Edge.WindCnt2 := E.WindCnt2;
2456 E := E.NextInAEL; //ie get ready to calc WindCnt2
2457 end else
2458 begin
2459 //NonZero, Positive, or Negative filling ...
2460 if (E.WindCnt * E.WindDelta < 0) then
2461 begin
2462 //prev edge is 'decreasing' WindCount (WC) toward zero
2463 //so we're outside the previous polygon ...
2464 if (Abs(E.WindCnt) > 1) then
2465 begin
2466 //outside prev poly but still inside another.
2467 //when reversing direction of prev poly use the same WC
2468 if (E.WindDelta * Edge.WindDelta < 0) then
2469 Edge.WindCnt := E.WindCnt
2470 //otherwise continue to 'decrease' WC ...
2471 else Edge.WindCnt := E.WindCnt + Edge.WindDelta;
2472 end
2473 else
2474 //now outside all polys of same polytype so set own WC ...
2475 if Edge.WindDelta = 0 then Edge.WindCnt := 1
2476 else Edge.WindCnt := Edge.WindDelta;
2477 end else
2478 begin
2479 //prev edge is 'increasing' WindCount (WC) away from zero
2480 //so we're inside the previous polygon ...
2481 if (Edge.WindDelta = 0) then
2482 begin
2483 if (E.WindCnt < 0) then Edge.WindCnt := E.WindCnt -1
2484 else Edge.WindCnt := E.WindCnt +1;
2485 end
2486 //if wind direction is reversing prev then use same WC
2487 else if (E.WindDelta * Edge.WindDelta < 0) then
2488 Edge.WindCnt := E.WindCnt
2489 //otherwise add to WC ...
2490 else Edge.WindCnt := E.WindCnt + Edge.WindDelta;
2491 end;
2492 Edge.WindCnt2 := E.WindCnt2;
2493 E := E.NextInAEL; //ie get ready to calc WindCnt2
2494 end;
2495
2496 //update WindCnt2 ...
2497 if IsEvenOddAltFillType(Edge) then
2498 begin
2499 //even-odd filling ...
2500 while (E <> Edge) do
2501 begin
2502 if E.WindDelta = 0 then //do nothing (ie ignore lines)
2503 else if Edge.WindCnt2 = 0 then Edge.WindCnt2 := 1
2504 else Edge.WindCnt2 := 0;
2505 E := E.NextInAEL;
2506 end;
2507 end else
2508 begin
2509 //NonZero, Positive, or Negative filling ...
2510 while (E <> Edge) do
2511 begin
2512 Inc(Edge.WindCnt2, E.WindDelta);
2513 E := E.NextInAEL;
2514 end;
2515 end;
2516 end;
2517 //------------------------------------------------------------------------------
2518
TClipper.IsEvenOddFillTypenull2519 function TClipper.IsEvenOddFillType(Edge: PEdge): Boolean;
2520 begin
2521 if Edge.PolyType = ptSubject then
2522 Result := FSubjFillType = pftEvenOdd else
2523 Result := FClipFillType = pftEvenOdd;
2524 end;
2525 //------------------------------------------------------------------------------
2526
IsEvenOddAltFillTypenull2527 function TClipper.IsEvenOddAltFillType(Edge: PEdge): Boolean;
2528 begin
2529 if Edge.PolyType = ptSubject then
2530 Result := FClipFillType = pftEvenOdd else
2531 Result := FSubjFillType = pftEvenOdd;
2532 end;
2533 //------------------------------------------------------------------------------
2534
TClipper.IsContributingnull2535 function TClipper.IsContributing(Edge: PEdge): Boolean;
2536 var
2537 Pft, Pft2: TPolyFillType;
2538 begin
2539 if Edge.PolyType = ptSubject then
2540 begin
2541 Pft := FSubjFillType;
2542 Pft2 := FClipFillType;
2543 end else
2544 begin
2545 Pft := FClipFillType;
2546 Pft2 := FSubjFillType
2547 end;
2548
2549 case Pft of
2550 pftEvenOdd: Result := (Edge.WindDelta <> 0) or (Edge.WindCnt = 1);
2551 pftNonZero: Result := abs(Edge.WindCnt) = 1;
2552 pftPositive: Result := (Edge.WindCnt = 1);
2553 else Result := (Edge.WindCnt = -1);
2554 end;
2555 if not Result then Exit;
2556
2557 case FClipType of
2558 ctIntersection:
2559 case Pft2 of
2560 pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 <> 0);
2561 pftPositive: Result := (Edge.WindCnt2 > 0);
2562 pftNegative: Result := (Edge.WindCnt2 < 0);
2563 end;
2564 ctUnion:
2565 case Pft2 of
2566 pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 = 0);
2567 pftPositive: Result := (Edge.WindCnt2 <= 0);
2568 pftNegative: Result := (Edge.WindCnt2 >= 0);
2569 end;
2570 ctDifference:
2571 if Edge.PolyType = ptSubject then
2572 case Pft2 of
2573 pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 = 0);
2574 pftPositive: Result := (Edge.WindCnt2 <= 0);
2575 pftNegative: Result := (Edge.WindCnt2 >= 0);
2576 end
2577 else
2578 case Pft2 of
2579 pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 <> 0);
2580 pftPositive: Result := (Edge.WindCnt2 > 0);
2581 pftNegative: Result := (Edge.WindCnt2 < 0);
2582 end;
2583 ctXor:
2584 if Edge.WindDelta = 0 then //XOr always contributing unless open
2585 case Pft2 of
2586 pftEvenOdd, pftNonZero: Result := (Edge.WindCnt2 = 0);
2587 pftPositive: Result := (Edge.WindCnt2 <= 0);
2588 pftNegative: Result := (Edge.WindCnt2 >= 0);
2589 end;
2590 end;
2591 end;
2592 //------------------------------------------------------------------------------
2593
AddLocalMinPolynull2594 function TClipper.AddLocalMinPoly(E1, E2: PEdge; const Pt: TIntPoint): POutPt;
2595 var
2596 E, prevE: PEdge;
2597 OutPt: POutPt;
2598 X1, X2: cInt;
2599 begin
2600 if (E2.Dx = Horizontal) or (E1.Dx > E2.Dx) then
2601 begin
2602 Result := AddOutPt(E1, Pt);
2603 E2.OutIdx := E1.OutIdx;
2604 E1.Side := esLeft;
2605 E2.Side := esRight;
2606 E := E1;
2607 if E.PrevInAEL = E2 then
2608 prevE := E2.PrevInAEL
2609 else
2610 prevE := E.PrevInAEL;
2611 end else
2612 begin
2613 Result := AddOutPt(E2, Pt);
2614 E1.OutIdx := E2.OutIdx;
2615 E1.Side := esRight;
2616 E2.Side := esLeft;
2617
2618 E := E2;
2619 if E.PrevInAEL = E1 then
2620 prevE := E1.PrevInAEL
2621 else
2622 prevE := E.PrevInAEL;
2623 end;
2624
2625 if Assigned(prevE) and (prevE.OutIdx >= 0) and
2626 (prevE.Top.Y < Pt.Y) and (E.Top.Y < Pt.Y) then
2627 begin
2628 X1 := TopX(prevE, Pt.Y);
2629 X2 := TopX(E, Pt.Y);
2630 if (X1 = X2) and
2631 SlopesEqual(IntPoint(X1, Pt.Y), prevE.Top, IntPoint(X2, Pt.Y), E.Top,
2632 FUse64BitRange) and (E.WindDelta <> 0) and (prevE.WindDelta <> 0) then
2633 begin
2634 OutPt := AddOutPt(prevE, Pt);
2635 AddJoin(Result, OutPt, E.Top);
2636 end;
2637 end;
2638 end;
2639 //------------------------------------------------------------------------------
2640
2641 procedure TClipper.AddLocalMaxPoly(E1, E2: PEdge; const Pt: TIntPoint);
2642 begin
2643 AddOutPt(E1, Pt);
2644 if E2.WindDelta = 0 then AddOutPt(E2, Pt);
2645 if (E1.OutIdx = E2.OutIdx) then
2646 begin
2647 E1.OutIdx := Unassigned;
2648 E2.OutIdx := Unassigned;
2649 end
2650 else if E1.OutIdx < E2.OutIdx then
2651 AppendPolygon(E1, E2)
2652 else
2653 AppendPolygon(E2, E1);
2654 end;
2655 //------------------------------------------------------------------------------
2656
2657 procedure TClipper.AddEdgeToSEL(Edge: PEdge);
2658 begin
2659 //SEL pointers in PEdge are use to build transient lists of horizontal edges.
2660 //However, since we don't need to worry about processing order, all additions
2661 //are made to the front of the list ...
2662 if not Assigned(FSortedEdges) then
2663 begin
2664 FSortedEdges := Edge;
2665 Edge.PrevInSEL := nil;
2666 Edge.NextInSEL := nil;
2667 end else
2668 begin
2669 Edge.NextInSEL := FSortedEdges;
2670 Edge.PrevInSEL := nil;
2671 FSortedEdges.PrevInSEL := Edge;
2672 FSortedEdges := Edge;
2673 end;
2674 end;
2675 //------------------------------------------------------------------------------
2676
PopEdgeFromSELnull2677 function TClipper.PopEdgeFromSEL(out E: PEdge): Boolean;
2678 begin
2679 //Pop edge from front of SEL (ie SEL is a FILO list)
2680 E := FSortedEdges;
2681 Result := assigned(E);
2682 if not Result then Exit;
2683 FSortedEdges := E.NextInSEL;
2684 if Assigned(FSortedEdges) then FSortedEdges.PrevInSEL := nil;
2685 E.NextInSEL := nil;
2686 E.PrevInSEL := nil;
2687 end;
2688 //------------------------------------------------------------------------------
2689
2690 procedure TClipper.CopyAELToSEL;
2691 var
2692 E: PEdge;
2693 begin
2694 E := FActiveEdges;
2695 FSortedEdges := E;
2696 while Assigned(E) do
2697 begin
2698 E.PrevInSEL := E.PrevInAEL;
2699 E.NextInSEL := E.NextInAEL;
2700 E := E.NextInAEL;
2701 end;
2702 end;
2703 //------------------------------------------------------------------------------
2704
AddOutPtnull2705 function TClipper.AddOutPt(E: PEdge; const Pt: TIntPoint): POutPt;
2706 var
2707 OutRec: POutRec;
2708 PrevOp, Op: POutPt;
2709 ToFront: Boolean;
2710 begin
2711 if E.OutIdx < 0 then
2712 begin
2713 OutRec := CreateOutRec;
2714 OutRec.IsOpen := (E.WindDelta = 0);
2715 new(Result);
2716 OutRec.Pts := Result;
2717 Result.Pt := Pt;
2718 Result.Next := Result;
2719 Result.Prev := Result;
2720 Result.Idx := OutRec.Idx;
2721 if not OutRec.IsOpen then
2722 SetHoleState(E, OutRec);
2723 E.OutIdx := OutRec.Idx;
2724 end else
2725 begin
2726 ToFront := E.Side = esLeft;
2727 OutRec := FPolyOutList[E.OutIdx];
2728 //OutRec.Pts is the 'left-most' point & OutRec.Pts.Prev is the 'right-most'
2729 Op := OutRec.Pts;
2730 if ToFront then PrevOp := Op else PrevOp := Op.Prev;
2731 if PointsEqual(Pt, PrevOp.Pt) then
2732 begin
2733 Result := PrevOp;
2734 Exit;
2735 end;
2736 new(Result);
2737 Result.Pt := Pt;
2738 Result.Idx := OutRec.Idx;
2739 Result.Next := Op;
2740 Result.Prev := Op.Prev;
2741 Op.Prev.Next := Result;
2742 Op.Prev := Result;
2743 if ToFront then OutRec.Pts := Result;
2744 end;
2745 end;
2746 //------------------------------------------------------------------------------
2747
2748 procedure TClipper.AddJoin(Op1, Op2: POutPt; const OffPt: TIntPoint);
2749 var
2750 Jr: PJoin;
2751 begin
2752 new(Jr);
2753 Jr.OutPt1 := Op1;
2754 Jr.OutPt2 := Op2;
2755 Jr.OffPt := OffPt;
2756 FJoinList.add(Jr);
2757 end;
2758 //------------------------------------------------------------------------------
2759
2760 procedure TClipper.ClearJoins;
2761 var
2762 I: Integer;
2763 begin
2764 for I := 0 to FJoinList.count -1 do
2765 Dispose(PJoin(fJoinList[I]));
2766 FJoinList.Clear;
2767 end;
2768 //------------------------------------------------------------------------------
2769
2770 procedure TClipper.AddGhostJoin(OutPt: POutPt; const OffPt: TIntPoint);
2771 var
2772 Jr: PJoin;
2773 begin
2774 //Ghost joins are used to find horizontal edges at the top of one scanbeam
2775 //that coincide with horizontal edges at the bottom of the next. Ghost joins
2776 //are converted to real joins when match ups occur.
2777 new(Jr);
2778 Jr.OutPt1 := OutPt;
2779 Jr.OffPt := OffPt;
2780 FGhostJoinList.Add(Jr);
2781 end;
2782 //------------------------------------------------------------------------------
2783
2784 procedure TClipper.ClearGhostJoins;
2785 var
2786 I: Integer;
2787 begin
2788 for I := 0 to FGhostJoinList.Count -1 do
2789 Dispose(PJoin(FGhostJoinList[I]));
2790 FGhostJoinList.Clear;
2791 end;
2792 //------------------------------------------------------------------------------
2793
2794 procedure SwapPoints(var Pt1, Pt2: TIntPoint);
2795 var
2796 Tmp: TIntPoint;
2797 begin
2798 Tmp := Pt1;
2799 Pt1 := Pt2;
2800 Pt2 := Tmp;
2801 end;
2802 //------------------------------------------------------------------------------
2803
2804 function HorzSegmentsOverlap(seg1a, seg1b, seg2a, seg2b: cInt): Boolean;
2805 begin
2806 if (seg1a > seg1b) then Swap(seg1a, seg1b);
2807 if (seg2a > seg2b) then Swap(seg2a, seg2b);
2808 Result := (seg1a < seg2b) and (seg2a < seg1b);
2809 end;
2810 //------------------------------------------------------------------------------
2811
2812 function E2InsertsBeforeE1(E1, E2: PEdge): Boolean;
2813 {$IFDEF INLINING} inline; {$ENDIF}
2814 begin
2815 if E2.Curr.X = E1.Curr.X then
2816 begin
2817 //nb: E1.Top.Y == E2.Bot.Y only occurs when an earlier Rb is horizontal
2818 if E2.Top.Y > E1.Top.Y then
2819 Result := E2.Top.X < TopX(E1, E2.Top.Y) else
2820 Result := E1.Top.X > TopX(E2, E1.Top.Y);
2821 end else
2822 Result := E2.Curr.X < E1.Curr.X;
2823 end;
2824 //----------------------------------------------------------------------
2825
2826 procedure TClipper.InsertLocalMinimaIntoAEL(const BotY: cInt);
2827
2828 procedure InsertEdgeIntoAEL(Edge, StartEdge: PEdge);
2829 begin
2830 if not Assigned(FActiveEdges) then
2831 begin
2832 Edge.PrevInAEL := nil;
2833 Edge.NextInAEL := nil;
2834 FActiveEdges := Edge;
2835 end
2836 else if not Assigned(StartEdge) and
2837 E2InsertsBeforeE1(FActiveEdges, Edge) then
2838 begin
2839 Edge.PrevInAEL := nil;
2840 Edge.NextInAEL := FActiveEdges;
2841 FActiveEdges.PrevInAEL := Edge;
2842 FActiveEdges := Edge;
2843 end else
2844 begin
2845 if not Assigned(StartEdge) then StartEdge := FActiveEdges;
2846 while Assigned(StartEdge.NextInAEL) and
2847 not E2InsertsBeforeE1(StartEdge.NextInAEL, Edge) do
2848 StartEdge := StartEdge.NextInAEL;
2849 Edge.NextInAEL := StartEdge.NextInAEL;
2850 if Assigned(StartEdge.NextInAEL) then
2851 StartEdge.NextInAEL.PrevInAEL := Edge;
2852 Edge.PrevInAEL := StartEdge;
2853 StartEdge.NextInAEL := Edge;
2854 end;
2855 end;
2856 //----------------------------------------------------------------------
2857
2858 var
2859 I: Integer;
2860 E: PEdge;
2861 Lb, Rb: PEdge;
2862 Jr: PJoin;
2863 Op1, Op2: POutPt;
2864 LocMin: PLocalMinimum;
2865 begin
2866 //Add any local minima at BotY ...
2867 while PopLocalMinima(BotY, LocMin) do
2868 begin
2869 Lb := LocMin.LeftBound;
2870 Rb := LocMin.RightBound;
2871 Op1 := nil;
2872 if not assigned(Lb) then
2873 begin
2874 InsertEdgeIntoAEL(Rb, nil);
2875 SetWindingCount(Rb);
2876 if IsContributing(Rb) then
2877 Op1 := AddOutPt(Rb, Rb.Bot);
2878 end
2879 else if not assigned(Rb) then
2880 begin
2881 InsertEdgeIntoAEL(Lb, nil);
2882 SetWindingCount(Lb);
2883 if IsContributing(Lb) then
2884 Op1 := AddOutPt(Lb, Lb.Bot);
2885 InsertScanbeam(Lb.Top.Y);
2886 end else
2887 begin
2888 InsertEdgeIntoAEL(Lb, nil);
2889 InsertEdgeIntoAEL(Rb, Lb);
2890 SetWindingCount(Lb);
2891 Rb.WindCnt := Lb.WindCnt;
2892 Rb.WindCnt2 := Lb.WindCnt2;
2893 if IsContributing(Lb) then
2894 Op1 := AddLocalMinPoly(Lb, Rb, Lb.Bot);
2895 InsertScanbeam(Lb.Top.Y);
2896 end;
2897
2898 if Assigned(Rb) then
2899 begin
2900 if (Rb.Dx = Horizontal) then
2901 begin
2902 AddEdgeToSEL(Rb);
2903 if assigned(Rb.NextInLML) then
2904 InsertScanbeam(Rb.NextInLML.Top.Y);
2905 end else
2906 InsertScanbeam(Rb.Top.Y);
2907 end;
2908
2909 if not assigned(Lb) or not assigned(Rb) then Continue;
2910
2911 //if output polygons share an Edge with rb, they'll need joining later ...
2912 if assigned(Op1) and (Rb.Dx = Horizontal) and
2913 (FGhostJoinList.Count > 0) and (Rb.WindDelta <> 0) then
2914 begin
2915 for I := 0 to FGhostJoinList.Count -1 do
2916 begin
2917 //if the horizontal Rb and a 'ghost' horizontal overlap, then convert
2918 //the 'ghost' join to a real join ready for later ...
2919 Jr := PJoin(FGhostJoinList[I]);
2920 if HorzSegmentsOverlap(Jr.OutPt1.Pt.X, Jr.OffPt.X,
2921 Rb.Bot.X, Rb.Top.X) then
2922 AddJoin(Jr.OutPt1, Op1, Jr.OffPt);
2923 end;
2924 end;
2925
2926 if (Lb.OutIdx >= 0) and assigned(Lb.PrevInAEL) and
2927 (Lb.PrevInAEL.Curr.X = Lb.Bot.X) and
2928 (Lb.PrevInAEL.OutIdx >= 0) and
2929 SlopesEqual(Lb.PrevInAEL.Curr, Lb.PrevInAEL.Top,
2930 Lb.Curr, Lb.Top, FUse64BitRange) and
2931 (Lb.WindDelta <> 0) and (Lb.PrevInAEL.WindDelta <> 0) then
2932 begin
2933 Op2 := AddOutPt(Lb.PrevInAEL, Lb.Bot);
2934 AddJoin(Op1, Op2, Lb.Top);
2935 end;
2936
2937 if (Lb.NextInAEL <> Rb) then
2938 begin
2939 if (Rb.OutIdx >= 0) and (Rb.PrevInAEL.OutIdx >= 0) and
2940 SlopesEqual(Rb.PrevInAEL.Curr, Rb.PrevInAEL.Top,
2941 Rb.Curr, Rb.Top, FUse64BitRange) and
2942 (Rb.WindDelta <> 0) and (Rb.PrevInAEL.WindDelta <> 0) then
2943 begin
2944 Op2 := AddOutPt(Rb.PrevInAEL, Rb.Bot);
2945 AddJoin(Op1, Op2, Rb.Top);
2946 end;
2947
2948 E := Lb.NextInAEL;
2949 if Assigned(E) then
2950 while (E <> Rb) do
2951 begin
2952 //nb: For calculating winding counts etc, IntersectEdges() assumes
2953 //that param1 will be to the right of param2 ABOVE the intersection ...
2954 IntersectEdges(Rb, E, Lb.Curr);
2955 E := E.NextInAEL;
2956 end;
2957 end;
2958 end;
2959 end;
2960 //------------------------------------------------------------------------------
2961
2962 procedure TClipper.IntersectEdges(E1,E2: PEdge; Pt: TIntPoint);
2963 var
2964 E1Contributing, E2contributing: Boolean;
2965 E1FillType, E2FillType, E1FillType2, E2FillType2: TPolyFillType;
2966 E1Wc, E2Wc, E1Wc2, E2Wc2: Integer;
2967 begin
2968 {IntersectEdges}
2969 //E1 will be to the left of E2 BELOW the intersection. Therefore E1 is before
2970 //E2 in AEL except when E1 is being inserted at the intersection point ...
2971
2972 E1Contributing := (E1.OutIdx >= 0);
2973 E2contributing := (E2.OutIdx >= 0);
2974
2975 {$IFDEF use_xyz}
2976 SetZ(Pt, E1, E2, FZFillCallback);
2977 {$ENDIF}
2978
2979 {$IFDEF use_lines}
2980 //if either edge is on an OPEN path ...
2981 if (E1.WindDelta = 0) or (E2.WindDelta = 0) then
2982 begin
2983 //ignore subject-subject open path intersections ...
2984 if (E1.WindDelta = 0) AND (E2.WindDelta = 0) then Exit
2985 //if intersecting a subj line with a subj poly ...
2986 else if (E1.PolyType = E2.PolyType) and
2987 (E1.WindDelta <> E2.WindDelta) and (FClipType = ctUnion) then
2988 begin
2989 if (E1.WindDelta = 0) then
2990 begin
2991 if (E2Contributing) then
2992 begin
2993 AddOutPt(E1, pt);
2994 if (E1Contributing) then E1.OutIdx := Unassigned;
2995 end;
2996 end else
2997 begin
2998 if (E1Contributing) then
2999 begin
3000 AddOutPt(E2, pt);
3001 if (E2Contributing) then E2.OutIdx := Unassigned;
3002 end;
3003 end;
3004 end
3005 else if (E1.PolyType <> E2.PolyType) then
3006 begin
3007 //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) = 1 ...
3008 if (E1.WindDelta = 0) and (Abs(E2.WindCnt) = 1) and
3009 ((FClipType <> ctUnion) or (E2.WindCnt2 = 0)) then
3010 begin
3011 AddOutPt(E1, Pt);
3012 if E1Contributing then E1.OutIdx := Unassigned;
3013 end
3014 else if (E2.WindDelta = 0) and (Abs(E1.WindCnt) = 1) and
3015 ((FClipType <> ctUnion) or (E1.WindCnt2 = 0)) then
3016 begin
3017 AddOutPt(E2, Pt);
3018 if E2Contributing then E2.OutIdx := Unassigned;
3019 end
3020 end;
3021 Exit;
3022 end;
3023 {$ENDIF}
3024
3025 //update winding counts...
3026 //assumes that E1 will be to the right of E2 ABOVE the intersection
3027 if E1.PolyType = E2.PolyType then
3028 begin
3029 if IsEvenOddFillType(E1) then
3030 begin
3031 E1Wc := E1.WindCnt;
3032 E1.WindCnt := E2.WindCnt;
3033 E2.WindCnt := E1Wc;
3034 end else
3035 begin
3036 if E1.WindCnt + E2.WindDelta = 0 then
3037 E1.WindCnt := -E1.WindCnt else
3038 Inc(E1.WindCnt, E2.WindDelta);
3039 if E2.WindCnt - E1.WindDelta = 0 then
3040 E2.WindCnt := -E2.WindCnt else
3041 Dec(E2.WindCnt, E1.WindDelta);
3042 end;
3043 end else
3044 begin
3045 if not IsEvenOddFillType(E2) then Inc(E1.WindCnt2, E2.WindDelta)
3046 else if E1.WindCnt2 = 0 then E1.WindCnt2 := 1
3047 else E1.WindCnt2 := 0;
3048
3049 if not IsEvenOddFillType(E1) then Dec(E2.WindCnt2, E1.WindDelta)
3050 else if E2.WindCnt2 = 0 then E2.WindCnt2 := 1
3051 else E2.WindCnt2 := 0;
3052 end;
3053
3054 if E1.PolyType = ptSubject then
3055 begin
3056 E1FillType := FSubjFillType;
3057 E1FillType2 := FClipFillType;
3058 end else
3059 begin
3060 E1FillType := FClipFillType;
3061 E1FillType2 := FSubjFillType;
3062 end;
3063 if E2.PolyType = ptSubject then
3064 begin
3065 E2FillType := FSubjFillType;
3066 E2FillType2 := FClipFillType;
3067 end else
3068 begin
3069 E2FillType := FClipFillType;
3070 E2FillType2 := FSubjFillType;
3071 end;
3072
3073 case E1FillType of
3074 pftPositive: E1Wc := E1.WindCnt;
3075 pftNegative : E1Wc := -E1.WindCnt;
3076 else E1Wc := abs(E1.WindCnt);
3077 end;
3078 case E2FillType of
3079 pftPositive: E2Wc := E2.WindCnt;
3080 pftNegative : E2Wc := -E2.WindCnt;
3081 else E2Wc := abs(E2.WindCnt);
3082 end;
3083
3084 if E1Contributing and E2contributing then
3085 begin
3086 if not (E1Wc in [0,1]) or not (E2Wc in [0,1]) or
3087 ((E1.PolyType <> E2.PolyType) and (fClipType <> ctXor)) then
3088 begin
3089 AddLocalMaxPoly(E1, E2, Pt);
3090 end else
3091 begin
3092 AddOutPt(E1, Pt);
3093 AddOutPt(E2, Pt);
3094 SwapSides(E1, E2);
3095 SwapPolyIndexes(E1, E2);
3096 end;
3097 end else if E1Contributing then
3098 begin
3099 if (E2Wc = 0) or (E2Wc = 1) then
3100 begin
3101 AddOutPt(E1, Pt);
3102 SwapSides(E1, E2);
3103 SwapPolyIndexes(E1, E2);
3104 end;
3105 end
3106 else if E2contributing then
3107 begin
3108 if (E1Wc = 0) or (E1Wc = 1) then
3109 begin
3110 AddOutPt(E2, Pt);
3111 SwapSides(E1, E2);
3112 SwapPolyIndexes(E1, E2);
3113 end;
3114 end
3115 else if ((E1Wc = 0) or (E1Wc = 1)) and ((E2Wc = 0) or (E2Wc = 1)) then
3116 begin
3117 //neither Edge is currently contributing ...
3118
3119 case E1FillType2 of
3120 pftPositive: E1Wc2 := E1.WindCnt2;
3121 pftNegative : E1Wc2 := -E1.WindCnt2;
3122 else E1Wc2 := abs(E1.WindCnt2);
3123 end;
3124 case E2FillType2 of
3125 pftPositive: E2Wc2 := E2.WindCnt2;
3126 pftNegative : E2Wc2 := -E2.WindCnt2;
3127 else E2Wc2 := abs(E2.WindCnt2);
3128 end;
3129
3130 if (E1.PolyType <> E2.PolyType) then
3131 begin
3132 AddLocalMinPoly(E1, E2, Pt);
3133 end
3134 else if (E1Wc = 1) and (E2Wc = 1) then
3135 case FClipType of
3136 ctIntersection:
3137 if (E1Wc2 > 0) and (E2Wc2 > 0) then
3138 AddLocalMinPoly(E1, E2, Pt);
3139 ctUnion:
3140 if (E1Wc2 <= 0) and (E2Wc2 <= 0) then
3141 AddLocalMinPoly(E1, E2, Pt);
3142 ctDifference:
3143 if ((E1.PolyType = ptClip) and (E1Wc2 > 0) and (E2Wc2 > 0)) or
3144 ((E1.PolyType = ptSubject) and (E1Wc2 <= 0) and (E2Wc2 <= 0)) then
3145 AddLocalMinPoly(E1, E2, Pt);
3146 ctXor:
3147 AddLocalMinPoly(E1, E2, Pt);
3148 end
3149 else
3150 swapsides(E1,E2);
3151 end;
3152 end;
3153 //------------------------------------------------------------------------------
3154
FirstParamIsBottomPtnull3155 function FirstParamIsBottomPt(btmPt1, btmPt2: POutPt): Boolean;
3156 var
3157 Dx1n, Dx1p, Dx2n, Dx2p: Double;
3158 P: POutPt;
3159 begin
3160 //Precondition: bottom-points share the same vertex.
3161 //Use inverse slopes of adjacent edges (ie dx/dy) to determine the outer
3162 //polygon and hence the 'real' bottompoint.
3163 //nb: Slope is vertical when dx == 0. If the greater abs(dx) of param1
3164 //is greater than or equal both abs(dx) in param2 then param1 is outer.
3165 P := btmPt1.Prev;
3166 while PointsEqual(P.Pt, btmPt1.Pt) and (P <> btmPt1) do P := P.Prev;
3167 Dx1p := abs(GetDx(btmPt1.Pt, P.Pt));
3168 P := btmPt1.Next;
3169 while PointsEqual(P.Pt, btmPt1.Pt) and (P <> btmPt1) do P := P.Next;
3170 Dx1n := abs(GetDx(btmPt1.Pt, P.Pt));
3171
3172 P := btmPt2.Prev;
3173 while PointsEqual(P.Pt, btmPt2.Pt) and (P <> btmPt2) do P := P.Prev;
3174 Dx2p := abs(GetDx(btmPt2.Pt, P.Pt));
3175 P := btmPt2.Next;
3176 while PointsEqual(P.Pt, btmPt2.Pt) and (P <> btmPt2) do P := P.Next;
3177 Dx2n := abs(GetDx(btmPt2.Pt, P.Pt));
3178 if (Max(Dx1p, Dx1n) = Max(Dx2p, Dx2n)) and
3179 (Min(Dx1p, Dx1n) = Min(Dx2p, Dx2n)) then
3180 Result := Area(btmPt1) > 0 //if otherwise identical use orientation
3181 else
3182 Result := ((Dx1p >= Dx2p) and (Dx1p >= Dx2n)) or
3183 ((Dx1n >= Dx2p) and (Dx1n >= Dx2n));
3184 end;
3185 //------------------------------------------------------------------------------
3186
GetBottomPtnull3187 function GetBottomPt(PP: POutPt): POutPt;
3188 var
3189 P, Dups: POutPt;
3190 begin
3191 Dups := nil;
3192 P := PP.Next;
3193 while P <> PP do
3194 begin
3195 if P.Pt.Y > PP.Pt.Y then
3196 begin
3197 PP := P;
3198 Dups := nil;
3199 end
3200 else if (P.Pt.Y = PP.Pt.Y) and (P.Pt.X <= PP.Pt.X) then
3201 begin
3202 if (P.Pt.X < PP.Pt.X) then
3203 begin
3204 Dups := nil;
3205 PP := P;
3206 end else
3207 begin
3208 if (P.Next <> PP) and (P.Prev <> PP) then Dups := P;
3209 end;
3210 end;
3211 P := P.Next;
3212 end;
3213 if Assigned(Dups) then
3214 begin
3215 //there appears to be at least 2 vertices at bottom-most point so ...
3216 while Dups <> P do
3217 begin
3218 if not FirstParamIsBottomPt(P, Dups) then PP := Dups;
3219 Dups := Dups.Next;
3220 while not PointsEqual(Dups.Pt, PP.Pt) do Dups := Dups.Next;
3221 end;
3222 end;
3223 Result := PP;
3224 end;
3225 //------------------------------------------------------------------------------
3226
3227 procedure TClipper.SetHoleState(E: PEdge; OutRec: POutRec);
3228 var
3229 E2, eTmp: PEdge;
3230 begin
3231 //E.FirstLeft is the parent/container OutRec of E if any, and is the first
3232 //unpaired OutRec to the left in AEL. (Paired OutRecs will either be a
3233 //sibling of E or a sibling of one of its 'parents'.)
3234 eTmp := nil;
3235 E2 := E.PrevInAEL;
3236 while Assigned(E2) do
3237 begin
3238 if (E2.OutIdx >= 0) and (E2.WindDelta <> 0) then
3239 begin
3240 if not assigned(eTmp) then
3241 eTmp := E2
3242 else if (eTmp.OutIdx = E2.OutIdx) then
3243 eTmp := nil; //paired
3244 end;
3245 E2 := E2.PrevInAEL;
3246 end;
3247 if assigned(eTmp) then
3248 begin
3249 OutRec.FirstLeft := POutRec(fPolyOutList[eTmp.OutIdx]);
3250 OutRec.IsHole := not OutRec.FirstLeft.IsHole;
3251 end else
3252 begin
3253 OutRec.FirstLeft := nil;
3254 OutRec.IsHole := False;
3255 end;
3256 end;
3257 //------------------------------------------------------------------------------
3258
GetLowermostRecnull3259 function GetLowermostRec(OutRec1, OutRec2: POutRec): POutRec;
3260 var
3261 OutPt1, OutPt2: POutPt;
3262 begin
3263 if not assigned(OutRec1.BottomPt) then
3264 OutRec1.BottomPt := GetBottomPt(OutRec1.Pts);
3265 if not assigned(OutRec2.BottomPt) then
3266 OutRec2.BottomPt := GetBottomPt(OutRec2.Pts);
3267 OutPt1 := OutRec1.BottomPt;
3268 OutPt2 := OutRec2.BottomPt;
3269 if (OutPt1.Pt.Y > OutPt2.Pt.Y) then Result := OutRec1
3270 else if (OutPt1.Pt.Y < OutPt2.Pt.Y) then Result := OutRec2
3271 else if (OutPt1.Pt.X < OutPt2.Pt.X) then Result := OutRec1
3272 else if (OutPt1.Pt.X > OutPt2.Pt.X) then Result := OutRec2
3273 else if (OutPt1.Next = OutPt1) then Result := OutRec2
3274 else if (OutPt2.Next = OutPt2) then Result := OutRec1
3275 else if FirstParamIsBottomPt(OutPt1, OutPt2) then Result := OutRec1
3276 else Result := OutRec2;
3277 end;
3278 //------------------------------------------------------------------------------
3279
OutRec1RightOfOutRec2null3280 function OutRec1RightOfOutRec2(OutRec1, OutRec2: POutRec): Boolean;
3281 begin
3282 Result := True;
3283 repeat
3284 OutRec1 := OutRec1.FirstLeft;
3285 if OutRec1 = OutRec2 then Exit;
3286 until not Assigned(OutRec1);
3287 Result := False;
3288 end;
3289 //------------------------------------------------------------------------------
3290
GetOutRecnull3291 function TClipper.GetOutRec(Idx: integer): POutRec;
3292 begin
3293 Result := FPolyOutList[Idx];
3294 while Result <> FPolyOutList[Result.Idx] do
3295 Result := FPolyOutList[Result.Idx];
3296 end;
3297 //------------------------------------------------------------------------------
3298
3299 procedure TClipper.AppendPolygon(E1, E2: PEdge);
3300 var
3301 HoleStateRec, OutRec1, OutRec2: POutRec;
3302 P1_lft, P1_rt, P2_lft, P2_rt: POutPt;
3303 OKIdx, ObsoleteIdx: Integer;
3304 E: PEdge;
3305 begin
3306 OutRec1 := FPolyOutList[E1.OutIdx];
3307 OutRec2 := FPolyOutList[E2.OutIdx];
3308
3309 //First work out which polygon fragment has the correct hole state.
3310 //Since we're working from the bottom upward and left to right, the left most
3311 //and lowermost polygon is outermost and must have the correct hole state ...
3312 if OutRec1RightOfOutRec2(OutRec1, OutRec2) then HoleStateRec := OutRec2
3313 else if OutRec1RightOfOutRec2(OutRec2, OutRec1) then HoleStateRec := OutRec1
3314 else HoleStateRec := GetLowermostRec(OutRec1, OutRec2);
3315
3316 //get the start and ends of both output polygons and
3317 //join E2 poly onto E1 poly and delete pointers to E2 ...
3318
3319 P1_lft := OutRec1.Pts;
3320 P2_lft := OutRec2.Pts;
3321 P1_rt := P1_lft.Prev;
3322 P2_rt := P2_lft.Prev;
3323
3324 if E1.Side = esLeft then
3325 begin
3326 if E2.Side = esLeft then
3327 begin
3328 //z y x a b c
3329 ReversePolyPtLinks(P2_lft);
3330 P2_lft.Next := P1_lft;
3331 P1_lft.Prev := P2_lft;
3332 P1_rt.Next := P2_rt;
3333 P2_rt.Prev := P1_rt;
3334 OutRec1.Pts := P2_rt;
3335 end else
3336 begin
3337 //x y z a b c
3338 P2_rt.Next := P1_lft;
3339 P1_lft.Prev := P2_rt;
3340 P2_lft.Prev := P1_rt;
3341 P1_rt.Next := P2_lft;
3342 OutRec1.Pts := P2_lft;
3343 end;
3344 end else
3345 begin
3346 if E2.Side = esRight then
3347 begin
3348 //a b c z y x
3349 ReversePolyPtLinks(P2_lft);
3350 P1_rt.Next := P2_rt;
3351 P2_rt.Prev := P1_rt;
3352 P2_lft.Next := P1_lft;
3353 P1_lft.Prev := P2_lft;
3354 end else
3355 begin
3356 //a b c x y z
3357 P1_rt.Next := P2_lft;
3358 P2_lft.Prev := P1_rt;
3359 P1_lft.Prev := P2_rt;
3360 P2_rt.Next := P1_lft;
3361 end;
3362 end;
3363
3364 OutRec1.BottomPt := nil;
3365 if HoleStateRec = OutRec2 then
3366 begin
3367 if OutRec2.FirstLeft <> OutRec1 then
3368 OutRec1.FirstLeft := OutRec2.FirstLeft;
3369 OutRec1.IsHole := OutRec2.IsHole;
3370 end;
3371
3372 OutRec2.Pts := nil;
3373 OutRec2.BottomPt := nil;
3374 OutRec2.FirstLeft := OutRec1;
3375
3376 OKIdx := OutRec1.Idx;
3377 ObsoleteIdx := OutRec2.Idx;
3378
3379 E1.OutIdx := Unassigned; //safe because we only get here via AddLocalMaxPoly
3380 E2.OutIdx := Unassigned;
3381
3382 E := FActiveEdges;
3383 while Assigned(E) do
3384 begin
3385 if (E.OutIdx = ObsoleteIdx) then
3386 begin
3387 E.OutIdx := OKIdx;
3388 E.Side := E1.Side;
3389 Break;
3390 end;
3391 E := E.NextInAEL;
3392 end;
3393
3394 OutRec2.Idx := OutRec1.Idx;
3395 end;
3396 //------------------------------------------------------------------------------
3397
GetLastOutPtnull3398 function TClipper.GetLastOutPt(E: PEdge): POutPt;
3399 var
3400 OutRec: POutRec;
3401 begin
3402 OutRec := FPolyOutList[E.OutIdx];
3403 if E.Side = esLeft then
3404 Result := OutRec.Pts else
3405 Result := OutRec.Pts.Prev;
3406 end;
3407 //------------------------------------------------------------------------------
3408
3409 procedure TClipper.ProcessHorizontals;
3410 var
3411 E: PEdge;
3412 begin
3413 while PopEdgeFromSEL(E) do
3414 ProcessHorizontal(E);
3415 end;
3416 //------------------------------------------------------------------------------
3417
3418 function IsMinima(E: PEdge): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
3419 begin
3420 Result := Assigned(E) and (E.Prev.NextInLML <> E) and (E.Next.NextInLML <> E);
3421 end;
3422 //------------------------------------------------------------------------------
3423
3424 function IsMaxima(E: PEdge; const Y: cInt): Boolean;
3425 {$IFDEF INLINING} inline; {$ENDIF}
3426 begin
3427 Result := Assigned(E) and (E.Top.Y = Y) and not Assigned(E.NextInLML);
3428 end;
3429 //------------------------------------------------------------------------------
3430
3431 function IsIntermediate(E: PEdge; const Y: cInt): Boolean;
3432 {$IFDEF INLINING} inline; {$ENDIF}
3433 begin
3434 Result := (E.Top.Y = Y) and Assigned(E.NextInLML);
3435 end;
3436 //------------------------------------------------------------------------------
3437
3438 function GetMaximaPair(E: PEdge): PEdge;
3439 begin
3440 if PointsEqual(E.Next.Top, E.Top) and not assigned(E.Next.NextInLML) then
3441 Result := E.Next
3442 else if PointsEqual(E.Prev.Top, E.Top) and not assigned(E.Prev.NextInLML) then
3443 Result := E.Prev
3444 else
3445 Result := nil;
3446 end;
3447 //------------------------------------------------------------------------------
3448
3449 function GetMaximaPairEx(E: PEdge): PEdge;
3450 begin
3451 //as above but returns nil if MaxPair isn't in AEL (unless it's horizontal)
3452 Result := GetMaximaPair(E);
3453 if not assigned(Result) or (Result.OutIdx = Skip) or
3454 ((Result.NextInAEL = Result.PrevInAEL) and not IsHorizontal(Result)) then
3455 Result := nil;
3456 end;
3457 //------------------------------------------------------------------------------
3458
3459 procedure TClipper.SwapPositionsInSEL(E1, E2: PEdge);
3460 var
3461 Prev,Next: PEdge;
3462 begin
3463 if E1.NextInSEL = E2 then
3464 begin
3465 Next := E2.NextInSEL;
3466 if Assigned(Next) then Next.PrevInSEL := E1;
3467 Prev := E1.PrevInSEL;
3468 if Assigned(Prev) then Prev.NextInSEL := E2;
3469 E2.PrevInSEL := Prev;
3470 E2.NextInSEL := E1;
3471 E1.PrevInSEL := E2;
3472 E1.NextInSEL := Next;
3473 end
3474 else if E2.NextInSEL = E1 then
3475 begin
3476 Next := E1.NextInSEL;
3477 if Assigned(Next) then Next.PrevInSEL := E2;
3478 Prev := E2.PrevInSEL;
3479 if Assigned(Prev) then Prev.NextInSEL := E1;
3480 E1.PrevInSEL := Prev;
3481 E1.NextInSEL := E2;
3482 E2.PrevInSEL := E1;
3483 E2.NextInSEL := Next;
3484 end else
3485 begin
3486 Next := E1.NextInSEL;
3487 Prev := E1.PrevInSEL;
3488 E1.NextInSEL := E2.NextInSEL;
3489 if Assigned(E1.NextInSEL) then E1.NextInSEL.PrevInSEL := E1;
3490 E1.PrevInSEL := E2.PrevInSEL;
3491 if Assigned(E1.PrevInSEL) then E1.PrevInSEL.NextInSEL := E1;
3492 E2.NextInSEL := Next;
3493 if Assigned(E2.NextInSEL) then E2.NextInSEL.PrevInSEL := E2;
3494 E2.PrevInSEL := Prev;
3495 if Assigned(E2.PrevInSEL) then E2.PrevInSEL.NextInSEL := E2;
3496 end;
3497 if not Assigned(E1.PrevInSEL) then FSortedEdges := E1
3498 else if not Assigned(E2.PrevInSEL) then FSortedEdges := E2;
3499 end;
3500 //------------------------------------------------------------------------------
3501
3502 function GetNextInAEL(E: PEdge; Direction: TDirection): PEdge;
3503 {$IFDEF INLINING} inline; {$ENDIF}
3504 begin
3505 if Direction = dLeftToRight then
3506 Result := E.NextInAEL else
3507 Result := E.PrevInAEL;
3508 end;
3509 //------------------------------------------------------------------------
3510
3511 procedure GetHorzDirection(HorzEdge: PEdge; out Dir: TDirection;
3512 out Left, Right: cInt); {$IFDEF INLINING} inline; {$ENDIF}
3513 begin
3514 if HorzEdge.Bot.X < HorzEdge.Top.X then
3515 begin
3516 Left := HorzEdge.Bot.X;
3517 Right := HorzEdge.Top.X;
3518 Dir := dLeftToRight;
3519 end else
3520 begin
3521 Left := HorzEdge.Top.X;
3522 Right := HorzEdge.Bot.X;
3523 Dir := dRightToLeft;
3524 end;
3525 end;
3526 //------------------------------------------------------------------------
3527
3528 procedure TClipper.ProcessHorizontal(HorzEdge: PEdge);
3529 var
3530 E, eNext, eNextHorz, ePrev, eMaxPair, eLastHorz: PEdge;
3531 HorzLeft, HorzRight: cInt;
3532 Direction: TDirection;
3533 Pt: TIntPoint;
3534 Op1, Op2: POutPt;
3535 IsLastHorz, IsOpen: Boolean;
3536 currMax: PMaxima;
3537 begin
3538 (*******************************************************************************
3539 * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or *
3540 * bottom of a scanbeam) are processed as if layered. The order in which HEs *
3541 * are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] *
3542 * (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), *
3543 * and with other non-horizontal edges [*]. Once these intersections are *
3544 * processed, intermediate HEs then 'promote' the Edge above (NextInLML) into *
3545 * the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. *
3546 *******************************************************************************)
3547
3548 (*******************************************************************************
3549 * \ nb: HE processing order doesn't matter / / *
3550 * \ / / *
3551 * { -------- \ ------------------- / \ - (3) o==========%==========o - } *
3552 * { o==========o (2) / \ . . } *
3553 * { . / \ . . } *
3554 * { ---- o===============#========*========*=====#==========o (1) ------- } *
3555 * / \ / \ / *
3556 *******************************************************************************)
3557
3558 GetHorzDirection(HorzEdge, Direction, HorzLeft, HorzRight);
3559 IsOpen := (HorzEdge.WindDelta = 0);
3560
3561 eLastHorz := HorzEdge;
3562 while Assigned(eLastHorz.NextInLML) and
3563 (eLastHorz.NextInLML.Dx = Horizontal) do
3564 eLastHorz := eLastHorz.NextInLML;
3565 if Assigned(eLastHorz.NextInLML) then
3566 eMaxPair := nil else
3567 eMaxPair := GetMaximaPair(eLastHorz);
3568
3569 Op1 := nil;
3570 currMax := FMaxima;
3571 //nb: FMaxima will only be assigned when the Simplify property is set true.
3572
3573 if assigned(currMax) then
3574 begin
3575 //get the first useful Maxima ...
3576 if (Direction = dLeftToRight) then
3577 begin
3578 while Assigned(currMax) and (currMax.X <= HorzEdge.Bot.X) do
3579 currMax := currMax.Next;
3580 if Assigned(currMax) and (currMax.X >= eLastHorz.Top.X) then
3581 currMax := nil;
3582 end else
3583 begin
3584 while Assigned(currMax.Next) and (currMax.Next.X < HorzEdge.Bot.X) do
3585 currMax := currMax.Next;
3586 if (currMax.X <= eLastHorz.Top.X) then currMax := nil;
3587 end;
3588 end;
3589
3590 while true do //loops through consec. horizontal edges
3591 begin
3592 IsLastHorz := (HorzEdge = eLastHorz);
3593 E := GetNextInAEL(HorzEdge, Direction);
3594 while Assigned(E) do
3595 begin
3596
3597 //this code block inserts extra coords into horizontal edges (in output
3598 //polygons) whereever maxima touch these horizontal edges. This helps
3599 //'simplifying' polygons (ie if the Simplify property is set).
3600 if assigned(currMax) then
3601 begin
3602 if (Direction = dLeftToRight) then
3603 begin
3604 while assigned(currMax) and (currMax.X < E.Curr.X) do
3605 begin
3606 if (HorzEdge.OutIdx >= 0) and not IsOpen then
3607 AddOutPt(HorzEdge, IntPoint(currMax.X, HorzEdge.Bot.Y));
3608 currMax := currMax.Next;
3609 end;
3610 end else
3611 begin
3612 while assigned(currMax) and (currMax.X > E.Curr.X) do
3613 begin
3614 if (HorzEdge.OutIdx >= 0) and not IsOpen then
3615 AddOutPt(HorzEdge, IntPoint(currMax.X, HorzEdge.Bot.Y));
3616 currMax := currMax.Prev;
3617 end;
3618 end;
3619 end;
3620
3621 if ((Direction = dLeftToRight) and (E.Curr.X > HorzRight)) or
3622 ((Direction = dRightToLeft) and (E.Curr.X < HorzLeft)) then
3623 Break;
3624
3625 //also break if we've got to the end of an intermediate horizontal edge
3626 //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
3627 if (E.Curr.X = HorzEdge.Top.X) and
3628 Assigned(HorzEdge.NextInLML) and (E.Dx < HorzEdge.NextInLML.Dx) then
3629 Break;
3630
3631 if (HorzEdge.OutIdx >= 0) and not IsOpen then //may be done multiple times
3632 begin
3633 {$IFDEF use_xyz}
3634 if (Direction = dLeftToRight) then SetZ(E.Curr, HorzEdge, E, FZFillCallback)
3635 else SetZ(E.Curr, E, HorzEdge, FZFillCallback);
3636 {$ENDIF}
3637 Op1 := AddOutPt(HorzEdge, E.Curr);
3638 eNextHorz := FSortedEdges;
3639 while Assigned(eNextHorz) do
3640 begin
3641 if (eNextHorz.OutIdx >= 0) and
3642 HorzSegmentsOverlap(HorzEdge.Bot.X,
3643 HorzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X) then
3644 begin
3645 Op2 := GetLastOutPt(eNextHorz);
3646 AddJoin(Op2, Op1, eNextHorz.Top);
3647 end;
3648 eNextHorz := eNextHorz.NextInSEL;
3649 end;
3650 AddGhostJoin(Op1, HorzEdge.Bot);
3651 end;
3652
3653 //OK, so far we're still in range of the horizontal Edge but make sure
3654 //we're at the last of consec. horizontals when matching with eMaxPair
3655 if (E = eMaxPair) and IsLastHorz then
3656 begin
3657 if HorzEdge.OutIdx >= 0 then
3658 AddLocalMaxPoly(HorzEdge, eMaxPair, HorzEdge.Top);
3659 deleteFromAEL(HorzEdge);
3660 deleteFromAEL(eMaxPair);
3661 Exit;
3662 end;
3663
3664 if (Direction = dLeftToRight) then
3665 begin
3666 Pt := IntPoint(E.Curr.X, HorzEdge.Curr.Y);
3667 IntersectEdges(HorzEdge, E, Pt);
3668 end else
3669 begin
3670 Pt := IntPoint(E.Curr.X, HorzEdge.Curr.Y);
3671 IntersectEdges(E, HorzEdge, Pt);
3672 end;
3673 eNext := GetNextInAEL(E, Direction);
3674 SwapPositionsInAEL(HorzEdge, E);
3675 E := eNext;
3676 end;
3677
3678 //Break out of loop if HorzEdge.NextInLML is not also horizontal ...
3679 if not Assigned(HorzEdge.NextInLML) or
3680 (HorzEdge.NextInLML.Dx <> Horizontal) then Break;
3681
3682 UpdateEdgeIntoAEL(HorzEdge);
3683 if (HorzEdge.OutIdx >= 0) then AddOutPt(HorzEdge, HorzEdge.Bot);
3684 GetHorzDirection(HorzEdge, Direction, HorzLeft, HorzRight);
3685 end;
3686
3687 if (HorzEdge.OutIdx >= 0) and not Assigned(Op1) then
3688 begin
3689 Op1 := GetLastOutPt(HorzEdge);
3690 eNextHorz := FSortedEdges;
3691 while Assigned(eNextHorz) do
3692 begin
3693 if (eNextHorz.OutIdx >= 0) and
3694 HorzSegmentsOverlap(HorzEdge.Bot.X,
3695 HorzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X) then
3696 begin
3697 Op2 := GetLastOutPt(eNextHorz);
3698 AddJoin(Op2, Op1, eNextHorz.Top);
3699 end;
3700 eNextHorz := eNextHorz.NextInSEL;
3701 end;
3702 AddGhostJoin(Op1, HorzEdge.Top);
3703 end;
3704
3705 if Assigned(HorzEdge.NextInLML) then
3706 begin
3707 if (HorzEdge.OutIdx >= 0) then
3708 begin
3709 Op1 := AddOutPt(HorzEdge, HorzEdge.Top);
3710
3711 UpdateEdgeIntoAEL(HorzEdge);
3712 if (HorzEdge.WindDelta = 0) then Exit;
3713 //nb: HorzEdge is no longer horizontal here
3714 ePrev := HorzEdge.PrevInAEL;
3715 eNext := HorzEdge.NextInAEL;
3716 if Assigned(ePrev) and (ePrev.Curr.X = HorzEdge.Bot.X) and
3717 (ePrev.Curr.Y = HorzEdge.Bot.Y) and (ePrev.WindDelta <> 0) and
3718 (ePrev.OutIdx >= 0) and (ePrev.Curr.Y > ePrev.Top.Y) and
3719 SlopesEqual(HorzEdge, ePrev, FUse64BitRange) then
3720 begin
3721 Op2 := AddOutPt(ePrev, HorzEdge.Bot);
3722 AddJoin(Op1, Op2, HorzEdge.Top);
3723 end
3724 else if Assigned(eNext) and (eNext.Curr.X = HorzEdge.Bot.X) and
3725 (eNext.Curr.Y = HorzEdge.Bot.Y) and (eNext.WindDelta <> 0) and
3726 (eNext.OutIdx >= 0) and (eNext.Curr.Y > eNext.Top.Y) and
3727 SlopesEqual(HorzEdge, eNext, FUse64BitRange) then
3728 begin
3729 Op2 := AddOutPt(eNext, HorzEdge.Bot);
3730 AddJoin(Op1, Op2, HorzEdge.Top);
3731 end;
3732 end else
3733 UpdateEdgeIntoAEL(HorzEdge);
3734 end else
3735 begin
3736 if (HorzEdge.OutIdx >= 0) then AddOutPt(HorzEdge, HorzEdge.Top);
3737 DeleteFromAEL(HorzEdge);
3738 end;
3739 end;
3740 //------------------------------------------------------------------------------
3741
ProcessIntersectionsnull3742 function TClipper.ProcessIntersections(const TopY: cInt): Boolean;
3743 begin
3744 Result := True;
3745 try
3746 BuildIntersectList(TopY);
3747 if (FIntersectList.Count = 0) then
3748 Exit
3749 else if FixupIntersectionOrder then
3750 ProcessIntersectList()
3751 else
3752 Result := False;
3753 finally
3754 DisposeIntersectNodes; //clean up if there's been an error
3755 FSortedEdges := nil;
3756 end;
3757 end;
3758 //------------------------------------------------------------------------------
3759
3760 procedure TClipper.DisposeIntersectNodes;
3761 var
3762 I: Integer;
3763 begin
3764 for I := 0 to FIntersectList.Count - 1 do
3765 Dispose(PIntersectNode(FIntersectList[I]));
3766 FIntersectList.Clear;
3767 end;
3768 //------------------------------------------------------------------------------
3769
3770 procedure TClipper.BuildIntersectList(const TopY: cInt);
3771 var
3772 E, eNext: PEdge;
3773 Pt: TIntPoint;
3774 IsModified: Boolean;
3775 NewNode: PIntersectNode;
3776 begin
3777 if not Assigned(fActiveEdges) then Exit;
3778
3779 //prepare for sorting ...
3780 E := FActiveEdges;
3781 FSortedEdges := E;
3782 while Assigned(E) do
3783 begin
3784 E.PrevInSEL := E.PrevInAEL;
3785 E.NextInSEL := E.NextInAEL;
3786 E.Curr.X := TopX(E, TopY);
3787 E := E.NextInAEL;
3788 end;
3789
3790 //bubblesort (because adjacent swaps are required) ...
3791 repeat
3792 IsModified := False;
3793 E := FSortedEdges;
3794 while Assigned(E.NextInSEL) do
3795 begin
3796 eNext := E.NextInSEL;
3797 if (E.Curr.X > eNext.Curr.X) then
3798 begin
3799 IntersectPointEx(E, eNext, Pt);
3800 if Pt.Y < TopY then
3801 Pt := IntPoint(TopX(E, TopY), TopY);
3802 new(NewNode);
3803 NewNode.Edge1 := E;
3804 NewNode.Edge2 := eNext;
3805 NewNode.Pt := Pt;
3806 FIntersectList.Add(NewNode);
3807
3808 SwapPositionsInSEL(E, eNext);
3809 IsModified := True;
3810 end else
3811 E := eNext;
3812 end;
3813 if Assigned(E.PrevInSEL) then
3814 E.PrevInSEL.NextInSEL := nil
3815 else Break;
3816 until not IsModified;
3817 end;
3818 //------------------------------------------------------------------------------
3819
3820 procedure TClipper.ProcessIntersectList;
3821 var
3822 I: Integer;
3823 begin
3824 for I := 0 to FIntersectList.Count - 1 do
3825 begin
3826 with PIntersectNode(FIntersectList[I])^ do
3827 begin
3828 IntersectEdges(Edge1, Edge2, Pt);
3829 SwapPositionsInAEL(Edge1, Edge2);
3830 end;
3831 dispose(PIntersectNode(FIntersectList[I]));
3832 end;
3833 FIntersectList.Clear;
3834 end;
3835 //------------------------------------------------------------------------------
3836
3837 procedure TClipper.DoMaxima(E: PEdge);
3838 var
3839 ENext, EMaxPair: PEdge;
3840 begin
3841 EMaxPair := GetMaximaPairEx(E);
3842 if not assigned(EMaxPair) then
3843 begin
3844 if E.OutIdx >= 0 then
3845 AddOutPt(E, E.Top);
3846 DeleteFromAEL(E);
3847 Exit;
3848 end;
3849
3850 ENext := E.NextInAEL;
3851 //rarely, with overlapping collinear edges (in open paths) ENext can be nil
3852 while Assigned(ENext) and (ENext <> EMaxPair) do
3853 begin
3854 IntersectEdges(E, ENext, E.Top);
3855 SwapPositionsInAEL(E, ENext);
3856 ENext := E.NextInAEL;
3857 end;
3858
3859 if (E.OutIdx = Unassigned) and (EMaxPair.OutIdx = Unassigned) then
3860 begin
3861 DeleteFromAEL(E);
3862 DeleteFromAEL(EMaxPair);
3863 end
3864 else if (E.OutIdx >= 0) and (EMaxPair.OutIdx >= 0) then
3865 begin
3866 if E.OutIdx >= 0 then
3867 AddLocalMaxPoly(E, EMaxPair, E.Top);
3868 deleteFromAEL(E);
3869 deleteFromAEL(eMaxPair);
3870 end
3871 {$IFDEF use_lines}
3872 else if E.WindDelta = 0 then
3873 begin
3874 if (E.OutIdx >= 0) then
3875 begin
3876 AddOutPt(E, E.Top);
3877 E.OutIdx := Unassigned;
3878 end;
3879 DeleteFromAEL(E);
3880
3881 if (EMaxPair.OutIdx >= 0) then
3882 begin
3883 AddOutPt(EMaxPair, E.Top);
3884 EMaxPair.OutIdx := Unassigned;
3885 end;
3886 DeleteFromAEL(EMaxPair);
3887 end
3888 {$ENDIF}
3889 else
3890 raise exception.Create(rsDoMaxima);
3891 end;
3892 //------------------------------------------------------------------------------
3893
3894 procedure TClipper.ProcessEdgesAtTopOfScanbeam(const TopY: cInt);
3895 var
3896 E, EMaxPair, ePrev, eNext: PEdge;
3897 Op, Op2: POutPt;
3898 IsMaximaEdge: Boolean;
3899 Pt: TIntPoint;
3900 begin
3901 (*******************************************************************************
3902 * Notes: Processing edges at scanline intersections (ie at the top or bottom *
3903 * of a scanbeam) needs to be done in multiple stages and in the correct order. *
3904 * Firstly, edges forming a 'maxima' need to be processed and then removed. *
3905 * Next, 'intermediate' and 'maxima' horizontal edges are processed. Then edges *
3906 * that intersect exactly at the top of the scanbeam are processed [%]. *
3907 * Finally, new minima are added and any intersects they create are processed. *
3908 *******************************************************************************)
3909
3910 (*******************************************************************************
3911 * \ / / \ / *
3912 * \ Horizontal minima / / \ / *
3913 * { -- o======================#====o -------- . ------------------- } *
3914 * { Horizontal maxima . % scanline intersect } *
3915 * { -- o=======================#===================#========o ---------- } *
3916 * | / / \ \ *
3917 * + maxima intersect / / \ \ *
3918 * /|\ / / \ \ *
3919 * / | \ / / \ \ *
3920 *******************************************************************************)
3921
3922 E := FActiveEdges;
3923 while Assigned(E) do
3924 begin
3925 //1. process maxima, treating them as if they're 'bent' horizontal edges,
3926 // but exclude maxima with Horizontal edges. nb: E can't be a Horizontal.
3927 IsMaximaEdge := IsMaxima(E, TopY);
3928 if IsMaximaEdge then
3929 begin
3930 EMaxPair := GetMaximaPairEx(E);
3931 IsMaximaEdge := not assigned(EMaxPair) or (EMaxPair.Dx <> Horizontal);
3932 end;
3933
3934 if IsMaximaEdge then
3935 begin
3936 if FStrictSimple then
3937 InsertMaxima(E.Top.X);
3938 //'E' might be removed from AEL, as may any following edges so ...
3939 ePrev := E.PrevInAEL;
3940 DoMaxima(E);
3941 if not Assigned(ePrev) then
3942 E := FActiveEdges else
3943 E := ePrev.NextInAEL;
3944 end else
3945 begin
3946 //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ...
3947 if IsIntermediate(E, TopY) and (E.NextInLML.Dx = Horizontal) then
3948 begin
3949 UpdateEdgeIntoAEL(E);
3950 if (E.OutIdx >= 0) then
3951 AddOutPt(E, E.Bot);
3952 AddEdgeToSEL(E);
3953 end else
3954 begin
3955 E.Curr.X := TopX(E, TopY);
3956 E.Curr.Y := TopY;
3957 {$IFDEF use_xyz}
3958 if E.Top.Y = TopY then e.Curr.Z := e.Top.Z
3959 else if (E.Bot.Y = TopY) then e.Curr.Z := E.Bot.Z else
3960 e.Curr.Z := 0;
3961 {$ENDIF}
3962 end;
3963
3964 //When StrictlySimple and 'e' is being touched by another edge, then
3965 //make sure both edges have a vertex here ...
3966 if FStrictSimple then
3967 begin
3968 ePrev := E.PrevInAEL;
3969 if (E.OutIdx >= 0) and (E.WindDelta <> 0) and
3970 Assigned(ePrev) and (ePrev.Curr.X = E.Curr.X) and
3971 (ePrev.OutIdx >= 0) and (ePrev.WindDelta <> 0) then
3972 begin
3973 Pt := E.Curr;
3974 {$IFDEF use_xyz}
3975 SetZ(Pt, ePrev, E, FZFillCallback);
3976 {$ENDIF}
3977 Op := AddOutPt(ePrev, Pt);
3978 Op2 := AddOutPt(E, Pt);
3979 AddJoin(Op, Op2, Pt); //strictly-simple (type-3) 'join'
3980 end;
3981 end;
3982
3983 E := E.NextInAEL;
3984 end;
3985 end;
3986
3987 //3. Process horizontals at the top of the scanbeam ...
3988 ProcessHorizontals;
3989 if FStrictSimple then DisposeMaximaList;
3990
3991 //4. Promote intermediate vertices ...
3992 E := FActiveEdges;
3993 while Assigned(E) do
3994 begin
3995 if IsIntermediate(E, TopY) then
3996 begin
3997 if (E.OutIdx >= 0) then
3998 Op := AddOutPt(E, E.Top) else
3999 Op := nil;
4000 UpdateEdgeIntoAEL(E);
4001
4002 //if output polygons share an Edge, they'll need joining later ...
4003 ePrev := E.PrevInAEL;
4004 eNext := E.NextInAEL;
4005 if Assigned(ePrev) and (ePrev.Curr.X = E.Bot.X) and
4006 (ePrev.Curr.Y = E.Bot.Y) and assigned(Op) and
4007 (ePrev.OutIdx >= 0) and (ePrev.Curr.Y > ePrev.Top.Y) and
4008 SlopesEqual(E.Curr, E.Top, ePrev.Curr, ePrev.Top, FUse64BitRange) and
4009 (E.WindDelta <> 0) and (ePrev.WindDelta <> 0) then
4010 begin
4011 Op2 := AddOutPt(ePrev, E.Bot);
4012 AddJoin(Op, Op2, E.Top);
4013 end
4014 else if Assigned(eNext) and (eNext.Curr.X = E.Bot.X) and
4015 (eNext.Curr.Y = E.Bot.Y) and assigned(Op) and
4016 (eNext.OutIdx >= 0) and (eNext.Curr.Y > eNext.Top.Y) and
4017 SlopesEqual(E.Curr, E.Top, eNext.Curr, eNext.Top, FUse64BitRange) and
4018 (E.WindDelta <> 0) and (eNext.WindDelta <> 0) then
4019 begin
4020 Op2 := AddOutPt(eNext, E.Bot);
4021 AddJoin(Op, Op2, E.Top);
4022 end;
4023 end;
4024 E := E.NextInAEL;
4025 end;
4026 end;
4027 //------------------------------------------------------------------------------
4028
BuildResultnull4029 function TClipper.BuildResult: TPaths;
4030 var
4031 I, J, K, Cnt: Integer;
4032 OutRec: POutRec;
4033 Op: POutPt;
4034 begin
4035 J := 0;
4036 SetLength(Result, FPolyOutList.Count);
4037 for I := 0 to FPolyOutList.Count -1 do
4038 if Assigned(fPolyOutList[I]) then
4039 begin
4040 OutRec := FPolyOutList[I];
4041 if not assigned(OutRec.Pts) then Continue;
4042
4043 Op := OutRec.Pts.Prev;
4044 Cnt := PointCount(Op);
4045 if (Cnt < 2) then Continue;
4046 SetLength(Result[J], Cnt);
4047 for K := 0 to Cnt -1 do
4048 begin
4049 Result[J][K] := Op.Pt;
4050 Op := Op.Prev;
4051 end;
4052 Inc(J);
4053 end;
4054 SetLength(Result, J);
4055 end;
4056 //------------------------------------------------------------------------------
4057
BuildResult2null4058 function TClipper.BuildResult2(PolyTree: TPolyTree): Boolean;
4059 var
4060 I, J, Cnt, CntAll: Integer;
4061 Op: POutPt;
4062 OutRec: POutRec;
4063 PolyNode: TPolyNode;
4064 begin
4065 try
4066 PolyTree.Clear;
4067 SetLength(PolyTree.FAllNodes, FPolyOutList.Count);
4068
4069 //add PolyTree ...
4070 CntAll := 0;
4071 for I := 0 to FPolyOutList.Count -1 do
4072 begin
4073 OutRec := fPolyOutList[I];
4074 Cnt := PointCount(OutRec.Pts);
4075 if (OutRec.IsOpen and (cnt < 2)) or
4076 (not outRec.IsOpen and (cnt < 3)) then Continue;
4077 FixHoleLinkage(OutRec);
4078
4079 PolyNode := TPolyNode.Create;
4080 PolyTree.FAllNodes[CntAll] := PolyNode;
4081 OutRec.PolyNode := PolyNode;
4082 Inc(CntAll);
4083 SetLength(PolyNode.FPath, Cnt);
4084 Op := OutRec.Pts.Prev;
4085 for J := 0 to Cnt -1 do
4086 begin
4087 PolyNode.FPath[J] := Op.Pt;
4088 Op := Op.Prev;
4089 end;
4090 end;
4091
4092 //fix Poly links ...
4093 SetLength(PolyTree.FAllNodes, CntAll);
4094 SetLength(PolyTree.FChilds, CntAll);
4095 for I := 0 to FPolyOutList.Count -1 do
4096 begin
4097 OutRec := fPolyOutList[I];
4098 if Assigned(OutRec.PolyNode) then
4099 begin
4100 if OutRec.IsOpen then
4101 begin
4102 OutRec.PolyNode.FIsOpen := true;
4103 PolyTree.AddChild(OutRec.PolyNode);
4104 end
4105 else if Assigned(OutRec.FirstLeft) and
4106 assigned(OutRec.FirstLeft.PolyNode)then
4107 OutRec.FirstLeft.PolyNode.AddChild(OutRec.PolyNode)
4108 else
4109 PolyTree.AddChild(OutRec.PolyNode);
4110 end;
4111 end;
4112 SetLength(PolyTree.FChilds, PolyTree.FCount);
4113 Result := True;
4114 except
4115 Result := False;
4116 end;
4117 end;
4118 //------------------------------------------------------------------------------
4119
4120 procedure TClipper.FixupOutPolyline(OutRec: POutRec);
4121 var
4122 PP, LastPP, TmpPP: POutPt;
4123 begin
4124 //remove duplicate points ...
4125 PP := OutRec.Pts;
4126 LastPP := PP.Prev;
4127 while (PP <> LastPP) do
4128 begin
4129 PP := PP.Next;
4130 //strip duplicate points ...
4131 if PointsEqual(PP.Pt, PP.Prev.Pt) then
4132 begin
4133 if PP = LastPP then LastPP := PP.Prev;
4134 TmpPP := PP.Prev;
4135 TmpPP.Next := PP.Next;
4136 PP.Next.Prev := TmpPP;
4137 dispose(PP);
4138 PP := TmpPP;
4139 end;
4140 end;
4141
4142 if (PP = PP.Prev) then
4143 begin
4144 Dispose(PP);
4145 OutRec.Pts := nil;
4146 Exit;
4147 end;
4148
4149 end;
4150 //------------------------------------------------------------------------------
4151
4152 procedure TClipper.FixupOutPolygon(OutRec: POutRec);
4153 var
4154 PP, Tmp, LastOK: POutPt;
4155 PreserveCol: Boolean;
4156 begin
4157 //remove duplicate points and collinear edges
4158 LastOK := nil;
4159 OutRec.BottomPt := nil; //flag as stale
4160 PP := OutRec.Pts;
4161 PreserveCol := FPreserveCollinear or FStrictSimple;
4162 while True do
4163 begin
4164 if (PP = PP.Prev) or (PP.Next = PP.Prev) then
4165 begin
4166 DisposePolyPts(PP);
4167 OutRec.Pts := nil;
4168 Exit;
4169 end;
4170
4171 //test for duplicate points and collinear edges ...
4172 if PointsEqual(PP.Pt, PP.Next.Pt) or PointsEqual(PP.Pt, PP.Prev.Pt) or
4173 (SlopesEqual(PP.Prev.Pt, PP.Pt, PP.Next.Pt, FUse64BitRange) and
4174 (not PreserveCol or
4175 not Pt2IsBetweenPt1AndPt3(PP.Prev.Pt, PP.Pt, PP.Next.Pt))) then
4176 begin
4177 //OK, we need to delete a point ...
4178 LastOK := nil;
4179 Tmp := PP;
4180 PP.Prev.Next := PP.Next;
4181 PP.Next.Prev := PP.Prev;
4182 PP := PP.Prev;
4183 dispose(Tmp);
4184 end
4185 else if PP = LastOK then Break
4186 else
4187 begin
4188 if not Assigned(LastOK) then LastOK := PP;
4189 PP := PP.Next;
4190 end;
4191 end;
4192 OutRec.Pts := PP;
4193 end;
4194 //------------------------------------------------------------------------------
4195
4196 function EdgesAdjacent(Inode: PIntersectNode): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
4197 begin
4198 Result := (Inode.Edge1.NextInSEL = Inode.Edge2) or
4199 (Inode.Edge1.PrevInSEL = Inode.Edge2);
4200 end;
4201 //------------------------------------------------------------------------------
4202
4203 function IntersectListSort(Node1, Node2: Pointer): Integer;
4204 var
4205 i: cInt;
4206 begin
4207 i := PIntersectNode(Node2).Pt.Y - PIntersectNode(Node1).Pt.Y;
4208 if i < 0 then Result := -1
4209 else if i > 0 then Result := 1
4210 else Result := 0;
4211 end;
4212 //------------------------------------------------------------------------------
4213
FixupIntersectionOrdernull4214 function TClipper.FixupIntersectionOrder: Boolean;
4215 var
4216 I, J, Cnt: Integer;
4217 Node: PIntersectNode;
4218 begin
4219 //pre-condition: intersections are sorted bottom-most first.
4220 //Now it's crucial that intersections are made only between adjacent edges,
4221 //and to ensure this the order of intersections may need adjusting ...
4222 Result := True;
4223 Cnt := FIntersectList.Count;
4224 if Cnt < 2 then exit;
4225
4226 CopyAELToSEL;
4227 {$IFDEF USEGENERICS}
4228 FIntersectList.Sort(TComparer<PIntersectNode>.Construct(
4229 function (const Node1, Node2 : PIntersectNode) : integer
4230 var
4231 i: cInt;
4232 begin
4233 i := PIntersectNode(Node2).Pt.Y - PIntersectNode(Node1).Pt.Y;
4234 if i < 0 then Result := -1
4235 else if i > 0 then Result := 1
4236 else Result := 0;
4237 end
4238 ));
4239 {$ELSE}
4240 FIntersectList.Sort(IntersectListSort);
4241 {$ENDIF}
4242 for I := 0 to Cnt - 1 do
4243 begin
4244 if not EdgesAdjacent(FIntersectList[I]) then
4245 begin
4246 J := I + 1;
4247 while (J < Cnt) and not EdgesAdjacent(FIntersectList[J]) do inc(J);
4248 if J = Cnt then
4249 begin
4250 Result := False;
4251 Exit; //error!!
4252 end;
4253 //Swap IntersectNodes ...
4254 Node := FIntersectList[I];
4255 FIntersectList[I] := FIntersectList[J];
4256 FIntersectList[J] := Node;
4257 end;
4258 with PIntersectNode(FIntersectList[I])^ do
4259 SwapPositionsInSEL(Edge1, Edge2);
4260 end;
4261 end;
4262 //------------------------------------------------------------------------------
4263
DupOutPtnull4264 function DupOutPt(OutPt: POutPt; InsertAfter: Boolean = true): POutPt;
4265 begin
4266 new(Result);
4267 Result.Pt := OutPt.Pt;
4268 Result.Idx := OutPt.Idx;
4269 if InsertAfter then
4270 begin
4271 Result.Next := OutPt.Next;
4272 Result.Prev := OutPt;
4273 OutPt.Next.Prev := Result;
4274 OutPt.Next := Result;
4275 end else
4276 begin
4277 Result.Prev := OutPt.Prev;
4278 Result.Next := OutPt;
4279 OutPt.Prev.Next := Result;
4280 OutPt.Prev := Result;
4281 end;
4282 end;
4283 //------------------------------------------------------------------------------
4284
JoinHorznull4285 function JoinHorz(Op1, Op1b, Op2, Op2b: POutPt;
4286 const Pt: TIntPoint; DiscardLeft: Boolean): Boolean;
4287 var
4288 Dir1, Dir2: TDirection;
4289 begin
4290 if Op1.Pt.X > Op1b.Pt.X then Dir1 := dRightToLeft else Dir1 := dLeftToRight;
4291 if Op2.Pt.X > Op2b.Pt.X then Dir2 := dRightToLeft else Dir2 := dLeftToRight;
4292 Result := Dir1 <> Dir2;
4293 if not Result then Exit;
4294
4295 //When DiscardLeft, we want Op1b to be on the left of Op1, otherwise we
4296 //want Op1b to be on the right. (And likewise with Op2 and Op2b.)
4297 //To facilitate this while inserting Op1b & Op2b when DiscardLeft == true,
4298 //make sure we're either AT or RIGHT OF Pt before adding Op1b, otherwise
4299 //make sure we're AT or LEFT OF Pt. (Likewise with Op2b.)
4300 if Dir1 = dLeftToRight then
4301 begin
4302 while (Op1.Next.Pt.X <= Pt.X) and
4303 (Op1.Next.Pt.X >= Op1.Pt.X) and (Op1.Next.Pt.Y = Pt.Y) do
4304 Op1 := Op1.Next;
4305 if DiscardLeft and (Op1.Pt.X <> Pt.X) then Op1 := Op1.Next;
4306 Op1b := DupOutPt(Op1, not DiscardLeft);
4307 if not PointsEqual(Op1b.Pt, Pt) then
4308 begin
4309 Op1 := Op1b;
4310 Op1.Pt := Pt;
4311 Op1b := DupOutPt(Op1, not DiscardLeft);
4312 end;
4313 end else
4314 begin
4315 while (Op1.Next.Pt.X >= Pt.X) and
4316 (Op1.Next.Pt.X <= Op1.Pt.X) and (Op1.Next.Pt.Y = Pt.Y) do
4317 Op1 := Op1.Next;
4318 if not DiscardLeft and (Op1.Pt.X <> Pt.X) then Op1 := Op1.Next;
4319 Op1b := DupOutPt(Op1, DiscardLeft);
4320 if not PointsEqual(Op1b.Pt, Pt) then
4321 begin
4322 Op1 := Op1b;
4323 Op1.Pt := Pt;
4324 Op1b := DupOutPt(Op1, DiscardLeft);
4325 end;
4326 end;
4327
4328 if Dir2 = dLeftToRight then
4329 begin
4330 while (Op2.Next.Pt.X <= Pt.X) and
4331 (Op2.Next.Pt.X >= Op2.Pt.X) and (Op2.Next.Pt.Y = Pt.Y) do
4332 Op2 := Op2.Next;
4333 if DiscardLeft and (Op2.Pt.X <> Pt.X) then Op2 := Op2.Next;
4334 Op2b := DupOutPt(Op2, not DiscardLeft);
4335 if not PointsEqual(Op2b.Pt, Pt) then
4336 begin
4337 Op2 := Op2b;
4338 Op2.Pt := Pt;
4339 Op2b := DupOutPt(Op2, not DiscardLeft);
4340 end;
4341 end else
4342 begin
4343 while (Op2.Next.Pt.X >= Pt.X) and
4344 (Op2.Next.Pt.X <= Op2.Pt.X) and (Op2.Next.Pt.Y = Pt.Y) do
4345 Op2 := Op2.Next;
4346 if not DiscardLeft and (Op2.Pt.X <> Pt.X) then Op2 := Op2.Next;
4347 Op2b := DupOutPt(Op2, DiscardLeft);
4348 if not PointsEqual(Op2b.Pt, Pt) then
4349 begin
4350 Op2 := Op2b;
4351 Op2.Pt := Pt;
4352 Op2b := DupOutPt(Op2, DiscardLeft);
4353 end;
4354 end;
4355
4356 if (Dir1 = dLeftToRight) = DiscardLeft then
4357 begin
4358 Op1.Prev := Op2;
4359 Op2.Next := Op1;
4360 Op1b.Next := Op2b;
4361 Op2b.Prev := Op1b;
4362 end
4363 else
4364 begin
4365 Op1.Next := Op2;
4366 Op2.Prev := Op1;
4367 Op1b.Prev := Op2b;
4368 Op2b.Next := Op1b;
4369 end;
4370 end;
4371 //------------------------------------------------------------------------------
4372
TClipper.JoinPointsnull4373 function TClipper.JoinPoints(Jr: PJoin; OutRec1, OutRec2: POutRec): Boolean;
4374 var
4375 Op1, Op1b, Op2, Op2b: POutPt;
4376 Pt: TIntPoint;
4377 Reverse1, Reverse2, DiscardLeftSide: Boolean;
4378 IsHorizontal: Boolean;
4379 Left, Right: cInt;
4380 begin
4381 Result := False;
4382 Op1 := Jr.OutPt1;
4383 Op2 := Jr.OutPt2;
4384
4385 //There are 3 kinds of joins for output polygons ...
4386 //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere
4387 //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal).
4388 //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
4389 //location at the bottom of the overlapping segment (& Join.OffPt is above).
4390 //3. StrictlySimple joins where edges touch but are not collinear and where
4391 //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point.
4392 IsHorizontal := (Jr.OutPt1.Pt.Y = Jr.OffPt.Y);
4393
4394 if IsHorizontal and PointsEqual(Jr.OffPt, Jr.OutPt1.Pt) and
4395 PointsEqual(Jr.OffPt, Jr.OutPt2.Pt) then
4396 begin
4397 //Strictly Simple join ...
4398 if (OutRec1 <> OutRec2) then
4399 Exit;
4400
4401 Op1b := Jr.OutPt1.Next;
4402 while (Op1b <> Op1) and
4403 PointsEqual(Op1b.Pt, Jr.OffPt) do Op1b := Op1b.Next;
4404 Reverse1 := (Op1b.Pt.Y > Jr.OffPt.Y);
4405 Op2b := Jr.OutPt2.Next;
4406 while (Op2b <> Op2) and
4407 PointsEqual(Op2b.Pt, Jr.OffPt) do Op2b := Op2b.Next;
4408 Reverse2 := (Op2b.Pt.Y > Jr.OffPt.Y);
4409 if (Reverse1 = Reverse2) then Exit;
4410
4411 if Reverse1 then
4412 begin
4413 Op1b := DupOutPt(Op1, False);
4414 Op2b := DupOutPt(Op2, True);
4415 Op1.Prev := Op2;
4416 Op2.Next := Op1;
4417 Op1b.Next := Op2b;
4418 Op2b.Prev := Op1b;
4419 Jr.OutPt1 := Op1;
4420 Jr.OutPt2 := Op1b;
4421 Result := True;
4422 end else
4423 begin
4424 Op1b := DupOutPt(Op1, True);
4425 Op2b := DupOutPt(Op2, False);
4426 Op1.Next := Op2;
4427 Op2.Prev := Op1;
4428 Op1b.Prev := Op2b;
4429 Op2b.Next := Op1b;
4430 Jr.OutPt1 := Op1;
4431 Jr.OutPt2 := Op1b;
4432 Result := True;
4433 end;
4434 end
4435 else if IsHorizontal then
4436 begin
4437 op1b := op1;
4438 while (op1.Prev.Pt.Y = op1.Pt.Y) and
4439 (op1.Prev <> Op1b) and (op1.Prev <> op2) do
4440 op1 := op1.Prev;
4441 while (op1b.Next.Pt.Y = op1b.Pt.Y) and
4442 (op1b.Next <> Op1) and (op1b.Next <> op2) do
4443 op1b := op1b.Next;
4444 if (op1b.Next = Op1) or (op1b.Next = op2) then Exit; //a flat 'polygon'
4445
4446 op2b := op2;
4447 while (op2.Prev.Pt.Y = op2.Pt.Y) and
4448 (op2.Prev <> Op2b) and (op2.Prev <> op1b) do
4449 op2 := op2.Prev;
4450 while (op2b.Next.Pt.Y = op2b.Pt.Y) and
4451 (op2b.Next <> Op2) and (op2b.Next <> op1) do
4452 op2b := op2b.Next;
4453 if (op2b.Next = Op2) or (op2b.Next = op1) then Exit; //a flat 'polygon'
4454
4455 //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges
4456 if not GetOverlap(Op1.Pt.X, Op1b.Pt.X, Op2.Pt.X, Op2b.Pt.X, Left, Right) then
4457 Exit;
4458
4459 //DiscardLeftSide: when joining overlapping edges, a spike will be created
4460 //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up
4461 //on the discard side as either may still be needed for other joins ...
4462 if (Op1.Pt.X >= Left) and (Op1.Pt.X <= Right) then
4463 begin
4464 Pt := Op1.Pt; DiscardLeftSide := Op1.Pt.X > Op1b.Pt.X;
4465 end else if (Op2.Pt.X >= Left) and (Op2.Pt.X <= Right) then
4466 begin
4467 Pt := Op2.Pt; DiscardLeftSide := Op2.Pt.X > Op2b.Pt.X;
4468 end else if (Op1b.Pt.X >= Left) and (Op1b.Pt.X <= Right) then
4469 begin
4470 Pt := Op1b.Pt; DiscardLeftSide := Op1b.Pt.X > Op1.Pt.X;
4471 end else
4472 begin
4473 Pt := Op2b.Pt; DiscardLeftSide := Op2b.Pt.X > Op2.Pt.X;
4474 end;
4475
4476 Result := JoinHorz(Op1, Op1b, Op2, Op2b, Pt, DiscardLeftSide);
4477 if not Result then Exit;
4478 Jr.OutPt1 := Op1;
4479 Jr.OutPt2 := Op2;
4480 end else
4481 begin
4482 //make sure the polygons are correctly oriented ...
4483 Op1b := Op1.Next;
4484 while PointsEqual(Op1b.Pt, Op1.Pt) and (Op1b <> Op1) do Op1b := Op1b.Next;
4485 Reverse1 := (Op1b.Pt.Y > Op1.Pt.Y) or
4486 not SlopesEqual(Op1.Pt, Op1b.Pt, Jr.OffPt, FUse64BitRange);
4487 if Reverse1 then
4488 begin
4489 Op1b := Op1.Prev;
4490 while PointsEqual(Op1b.Pt, Op1.Pt) and (Op1b <> Op1) do Op1b := Op1b.Prev;
4491 if (Op1b.Pt.Y > Op1.Pt.Y) or
4492 not SlopesEqual(Op1.Pt, Op1b.Pt, Jr.OffPt, FUse64BitRange) then Exit;
4493 end;
4494 Op2b := Op2.Next;
4495 while PointsEqual(Op2b.Pt, Op2.Pt) and (Op2b <> Op2) do Op2b := Op2b.Next;
4496 Reverse2 := (Op2b.Pt.Y > Op2.Pt.Y) or
4497 not SlopesEqual(Op2.Pt, Op2b.Pt, Jr.OffPt, FUse64BitRange);
4498 if Reverse2 then
4499 begin
4500 Op2b := Op2.Prev;
4501 while PointsEqual(Op2b.Pt, Op2.Pt) and (Op2b <> Op2) do Op2b := Op2b.Prev;
4502 if (Op2b.Pt.Y > Op2.Pt.Y) or
4503 not SlopesEqual(Op2.Pt, Op2b.Pt, Jr.OffPt, FUse64BitRange) then Exit;
4504 end;
4505
4506 if (Op1b = Op1) or (Op2b = Op2) or (Op1b = Op2b) or
4507 ((OutRec1 = OutRec2) and (Reverse1 = Reverse2)) then Exit;
4508
4509 if Reverse1 then
4510 begin
4511 Op1b := DupOutPt(Op1, False);
4512 Op2b := DupOutPt(Op2, True);
4513 Op1.Prev := Op2;
4514 Op2.Next := Op1;
4515 Op1b.Next := Op2b;
4516 Op2b.Prev := Op1b;
4517 Jr.OutPt1 := Op1;
4518 Jr.OutPt2 := Op1b;
4519 Result := True;
4520 end else
4521 begin
4522 Op1b := DupOutPt(Op1, True);
4523 Op2b := DupOutPt(Op2, False);
4524 Op1.Next := Op2;
4525 Op2.Prev := Op1;
4526 Op1b.Prev := Op2b;
4527 Op2b.Next := Op1b;
4528 Jr.OutPt1 := Op1;
4529 Jr.OutPt2 := Op1b;
4530 Result := True;
4531 end;
4532 end;
4533 end;
4534 //------------------------------------------------------------------------------
4535
4536 function ParseFirstLeft(FirstLeft: POutRec): POutRec;
4537 begin
4538 while Assigned(FirstLeft) and not Assigned(FirstLeft.Pts) do
4539 FirstLeft := FirstLeft.FirstLeft;
4540 Result := FirstLeft;
4541 end;
4542 //------------------------------------------------------------------------------
4543
4544 procedure TClipper.FixupFirstLefts1(OldOutRec, NewOutRec: POutRec);
4545 var
4546 I: Integer;
4547 outRec: POutRec;
4548 firstLeft: POutRec;
4549 begin
4550 //tests if NewOutRec contains the polygon before reassigning FirstLeft
4551 for I := 0 to FPolyOutList.Count -1 do
4552 begin
4553 outRec := fPolyOutList[I];
4554 firstLeft := ParseFirstLeft(outRec.FirstLeft);
4555 if Assigned(outRec.Pts) and (firstLeft = OldOutRec) then
4556 begin
4557 if Poly2ContainsPoly1(outRec.Pts, NewOutRec.Pts) then
4558 outRec.FirstLeft := NewOutRec;
4559 end;
4560 end;
4561 end;
4562 //------------------------------------------------------------------------------
4563
4564 procedure TClipper.FixupFirstLefts2(InnerOutRec, OuterOutRec: POutRec);
4565 var
4566 I: Integer;
4567 orfl, orec: POutRec;
4568 firstLeft: POutRec;
4569 begin
4570 //A polygon has split into two such that one is now the inner of the other.
4571 //It's possible that these polygons now wrap around other polygons, so check
4572 //every polygon that's also contained by OuterOutRec's FirstLeft container
4573 //(including nil) to see if they've become inner to the new inner polygon ...
4574 orfl := OuterOutRec.FirstLeft;
4575 for I := 0 to FPolyOutList.Count -1 do
4576 begin
4577 orec := POutRec(fPolyOutList[I]);
4578 if not Assigned(orec.Pts) or
4579 (orec = OuterOutRec) or (orec = InnerOutRec) then continue;
4580 firstLeft := ParseFirstLeft(orec.FirstLeft);
4581 if (firstLeft <> orfl) and (firstLeft <> InnerOutRec) and
4582 (firstLeft <> OuterOutRec) then Continue;
4583 if Poly2ContainsPoly1(orec.Pts, InnerOutRec.Pts) then
4584 orec.FirstLeft := InnerOutRec
4585 else if Poly2ContainsPoly1(orec.Pts, OuterOutRec.Pts) then
4586 orec.FirstLeft := OuterOutRec
4587 else if (orec.FirstLeft = InnerOutRec) or
4588 (orec.FirstLeft = OuterOutRec) then
4589 orec.FirstLeft := orfl;
4590 end;
4591 end;
4592 //------------------------------------------------------------------------------
4593
4594 procedure TClipper.FixupFirstLefts3(OldOutRec, NewOutRec: POutRec);
4595 var
4596 I: Integer;
4597 outRec: POutRec;
4598 firstLeft: POutRec;
4599 begin
4600 //same as FixupFirstLefts1 but doesn't call Poly2ContainsPoly1()
4601 for I := 0 to FPolyOutList.Count -1 do
4602 begin
4603 outRec := fPolyOutList[I];
4604 firstLeft := ParseFirstLeft(outRec.FirstLeft);
4605 if Assigned(outRec.Pts) and (firstLeft = OldOutRec) then
4606 outRec.FirstLeft := NewOutRec;
4607 end;
4608 end;
4609 //------------------------------------------------------------------------------
4610
4611 procedure TClipper.JoinCommonEdges;
4612 var
4613 I: Integer;
4614 Jr: PJoin;
4615 OutRec1, OutRec2, HoleStateRec: POutRec;
4616 begin
4617 for I := 0 to FJoinList.count -1 do
4618 begin
4619 Jr := FJoinList[I];
4620
4621 OutRec1 := GetOutRec(Jr.OutPt1.Idx);
4622 OutRec2 := GetOutRec(Jr.OutPt2.Idx);
4623
4624 if not Assigned(OutRec1.Pts) or not Assigned(OutRec2.Pts) then Continue;
4625 if OutRec1.IsOpen or OutRec2.IsOpen then Continue;
4626
4627 //get the polygon fragment with the correct hole state (FirstLeft)
4628 //before calling JoinPoints() ...
4629 if OutRec1 = OutRec2 then HoleStateRec := OutRec1
4630 else if OutRec1RightOfOutRec2(OutRec1, OutRec2) then HoleStateRec := OutRec2
4631 else if OutRec1RightOfOutRec2(OutRec2, OutRec1) then HoleStateRec := OutRec1
4632 else HoleStateRec := GetLowermostRec(OutRec1, OutRec2);
4633
4634 if not JoinPoints(Jr, OutRec1, OutRec2) then Continue;
4635
4636 if (OutRec1 = OutRec2) then
4637 begin
4638 //instead of joining two polygons, we've just created a new one by
4639 //splitting one polygon into two.
4640 OutRec1.Pts := Jr.OutPt1;
4641 OutRec1.BottomPt := nil;
4642 OutRec2 := CreateOutRec;
4643 OutRec2.Pts := Jr.OutPt2;
4644
4645 //update all OutRec2.Pts idx's ...
4646 UpdateOutPtIdxs(OutRec2);
4647
4648 //sort out the hole states of both polygon ...
4649 if Poly2ContainsPoly1(OutRec2.Pts, OutRec1.Pts) then
4650 begin
4651 //OutRec1 contains OutRec2 ...
4652 OutRec2.IsHole := not OutRec1.IsHole;
4653 OutRec2.FirstLeft := OutRec1;
4654
4655 if FUsingPolyTree then
4656 FixupFirstLefts2(OutRec2, OutRec1);
4657
4658 if (OutRec2.IsHole xor FReverseOutput) = (Area(OutRec2) > 0) then
4659 ReversePolyPtLinks(OutRec2.Pts);
4660 end else if Poly2ContainsPoly1(OutRec1.Pts, OutRec2.Pts) then
4661 begin
4662 //OutRec2 contains OutRec1 ...
4663 OutRec2.IsHole := OutRec1.IsHole;
4664 OutRec1.IsHole := not OutRec2.IsHole;
4665 OutRec2.FirstLeft := OutRec1.FirstLeft;
4666 OutRec1.FirstLeft := OutRec2;
4667 if FUsingPolyTree then
4668 FixupFirstLefts2(OutRec1, OutRec2);
4669
4670 if (OutRec1.IsHole xor FReverseOutput) = (Area(OutRec1) > 0) then
4671 ReversePolyPtLinks(OutRec1.Pts);
4672 end else
4673 begin
4674 //the 2 polygons are completely separate ...
4675 OutRec2.IsHole := OutRec1.IsHole;
4676 OutRec2.FirstLeft := OutRec1.FirstLeft;
4677
4678 //fixup FirstLeft pointers that may need reassigning to OutRec2
4679 if FUsingPolyTree then FixupFirstLefts1(OutRec1, OutRec2);
4680 end;
4681 end else
4682 begin
4683 //joined 2 polygons together ...
4684
4685 //delete the obsolete pointer ...
4686 OutRec2.Pts := nil;
4687 OutRec2.BottomPt := nil;
4688 OutRec2.Idx := OutRec1.Idx;
4689
4690 OutRec1.IsHole := HoleStateRec.IsHole;
4691 if HoleStateRec = OutRec2 then
4692 OutRec1.FirstLeft := OutRec2.FirstLeft;
4693 OutRec2.FirstLeft := OutRec1;
4694
4695 if FUsingPolyTree then
4696 FixupFirstLefts3(OutRec2, OutRec1);
4697 end;
4698 end;
4699 end;
4700 //------------------------------------------------------------------------------
4701
4702 procedure TClipper.DoSimplePolygons;
4703 var
4704 I: Integer;
4705 OutRec1, OutRec2: POutRec;
4706 Op, Op2, Op3, Op4: POutPt;
4707 begin
4708 I := 0;
4709 while I < FPolyOutList.Count do
4710 begin
4711 OutRec1 := POutRec(fPolyOutList[I]);
4712 inc(I);
4713 Op := OutRec1.Pts;
4714 if not assigned(Op) or OutRec1.IsOpen then Continue;
4715 repeat //for each Pt in Path until duplicate found do ...
4716 Op2 := Op.Next;
4717 while (Op2 <> OutRec1.Pts) do
4718 begin
4719 if (PointsEqual(Op.Pt, Op2.Pt) and
4720 (Op2.Next <> Op) and (Op2.Prev <> Op)) then
4721 begin
4722 //split the polygon into two ...
4723 Op3 := Op.Prev;
4724 Op4 := Op2.Prev;
4725 Op.Prev := Op4;
4726 Op4.Next := Op;
4727 Op2.Prev := Op3;
4728 Op3.Next := Op2;
4729
4730 OutRec1.Pts := Op;
4731
4732 OutRec2 := CreateOutRec;
4733 OutRec2.Pts := Op2;
4734 UpdateOutPtIdxs(OutRec2);
4735 if Poly2ContainsPoly1(OutRec2.Pts, OutRec1.Pts) then
4736 begin
4737 //OutRec2 is contained by OutRec1 ...
4738 OutRec2.IsHole := not OutRec1.IsHole;
4739 OutRec2.FirstLeft := OutRec1;
4740 if FUsingPolyTree then FixupFirstLefts2(OutRec2, OutRec1);
4741 end
4742 else
4743 if Poly2ContainsPoly1(OutRec1.Pts, OutRec2.Pts) then
4744 begin
4745 //OutRec1 is contained by OutRec2 ...
4746 OutRec2.IsHole := OutRec1.IsHole;
4747 OutRec1.IsHole := not OutRec2.IsHole;
4748 OutRec2.FirstLeft := OutRec1.FirstLeft;
4749 OutRec1.FirstLeft := OutRec2;
4750 if FUsingPolyTree then FixupFirstLefts2(OutRec1, OutRec2);
4751 end else
4752 begin
4753 //the 2 polygons are separate ...
4754 OutRec2.IsHole := OutRec1.IsHole;
4755 OutRec2.FirstLeft := OutRec1.FirstLeft;
4756 if FUsingPolyTree then FixupFirstLefts1(OutRec1, OutRec2);
4757 end;
4758 Op2 := Op; //ie get ready for the next iteration
4759 end;
4760 Op2 := Op2.Next;
4761 end;
4762 Op := Op.Next;
4763 until (Op = OutRec1.Pts);
4764 end;
4765 end;
4766
4767 //------------------------------------------------------------------------------
4768 // TClipperOffset methods
4769 //------------------------------------------------------------------------------
4770
4771 constructor TClipperOffset.Create(
4772 MiterLimit: Double = 2;
4773 ArcTolerance: Double = def_arc_tolerance);
4774 begin
4775 inherited Create;
4776 FPolyNodes := TPolyNode.Create;
4777 FLowest.X := -1;
4778 FMiterLimit := MiterLimit;
4779 FArcTolerance := ArcTolerance;
4780 end;
4781 //------------------------------------------------------------------------------
4782
4783 destructor TClipperOffset.Destroy;
4784 begin
4785 Clear;
4786 FPolyNodes.Free;
4787 inherited;
4788 end;
4789 //------------------------------------------------------------------------------
4790
4791 procedure TClipperOffset.Clear;
4792 var
4793 I: Integer;
4794 PolyNode: TPolyNode;
4795 begin
4796 for I := 0 to FPolyNodes.ChildCount -1 do
4797 begin
4798 PolyNode:= FPolyNodes.Childs[I];
4799 PolyNode.Free;
4800 end;
4801 FPolyNodes.FCount := 0;
4802 FPolyNodes.FBuffLen := 16;
4803 SetLength(FPolyNodes.FChilds, 16);
4804 FLowest.X := -1;
4805 end;
4806 //------------------------------------------------------------------------------
4807
4808 procedure TClipperOffset.AddPath(const Path: TPath;
4809 JoinType: TJoinType; EndType: TEndType);
4810 var
4811 I, J, K, HighI: Integer;
4812 NewNode: TPolyNode;
4813 ip: TIntPoint;
4814 begin
4815 HighI := High(Path);
4816 if HighI < 0 then Exit;
4817 NewNode := TPolyNode.Create;
4818 NewNode.FJoinType := JoinType;
4819 NewNode.FEndType := EndType;
4820
4821 //strip duplicate points from path and also get index to the lowest point ...
4822 if EndType in [etClosedLine, etClosedPolygon] then
4823 while (HighI > 0) and PointsEqual(Path[0], Path[HighI]) do dec(HighI);
4824 SetLength(NewNode.FPath, HighI +1);
4825 NewNode.FPath[0] := Path[0];
4826 J := 0; K := 0;
4827 for I := 1 to HighI do
4828 if not PointsEqual(NewNode.FPath[J], Path[I]) then
4829 begin
4830 inc(J);
4831 NewNode.FPath[J] := Path[I];
4832 if (NewNode.FPath[K].Y < Path[I].Y) or
4833 ((NewNode.FPath[K].Y = Path[I].Y) and
4834 (NewNode.FPath[K].X > Path[I].X)) then
4835 K := J;
4836 end;
4837 inc(J);
4838 if J < HighI +1 then
4839 SetLength(NewNode.FPath, J);
4840 if (EndType = etClosedPolygon) and (J < 3) then
4841 begin
4842 NewNode.free;
4843 Exit;
4844 end;
4845 FPolyNodes.AddChild(NewNode);
4846
4847 if EndType <> etClosedPolygon then Exit;
4848 //if this path's lowest pt is lower than all the others then update FLowest
4849 if (FLowest.X < 0) then
4850 begin
4851 FLowest := IntPoint(FPolyNodes.ChildCount -1, K);
4852 end else
4853 begin
4854 ip := FPolyNodes.Childs[FLowest.X].FPath[FLowest.Y];
4855 if (NewNode.FPath[K].Y > ip.Y) or
4856 ((NewNode.FPath[K].Y = ip.Y) and
4857 (NewNode.FPath[K].X < ip.X)) then
4858 FLowest := IntPoint(FPolyNodes.ChildCount -1, K);
4859 end;
4860 end;
4861 //------------------------------------------------------------------------------
4862
4863 procedure TClipperOffset.AddPaths(const Paths: TPaths;
4864 JoinType: TJoinType; EndType: TEndType);
4865 var
4866 I: Integer;
4867 begin
4868 for I := 0 to High(Paths) do AddPath(Paths[I], JoinType, EndType);
4869 end;
4870 //------------------------------------------------------------------------------
4871
4872 procedure TClipperOffset.FixOrientations;
4873 var
4874 I: Integer;
4875 begin
4876 //fixup orientations of all closed paths if the orientation of the
4877 //closed path with the lowermost vertex is wrong ...
4878 if (FLowest.X >= 0) and
4879 not Orientation(FPolyNodes.Childs[FLowest.X].FPath) then
4880 begin
4881 for I := 0 to FPolyNodes.ChildCount -1 do
4882 if FPolyNodes.Childs[I].FEndType = etClosedPolygon then
4883 FPolyNodes.Childs[I].FPath := ReversePath(FPolyNodes.Childs[I].FPath)
4884 else if (FPolyNodes.Childs[I].FEndType = etClosedLine) and
4885 Orientation(FPolyNodes.Childs[I].FPath) then
4886 FPolyNodes.Childs[I].FPath := ReversePath(FPolyNodes.Childs[I].FPath);
4887 end else
4888 begin
4889 for I := 0 to FPolyNodes.ChildCount -1 do
4890 if (FPolyNodes.Childs[I].FEndType = etClosedLine) and
4891 not Orientation(FPolyNodes.Childs[I].FPath) then
4892 FPolyNodes.Childs[I].FPath := ReversePath(FPolyNodes.Childs[I].FPath);
4893 end;
4894 end;
4895 //------------------------------------------------------------------------------
4896
4897 procedure TClipperOffset.DoOffset(Delta: Double);
4898 var
4899 I, J, K, Len, solCount: Integer;
4900 X, X2, Y, Steps, AbsDelta: Double;
4901 Node: TPolyNode;
4902 N: TDoublePoint;
4903 begin
4904 FSolution := nil;
4905 FDelta := Delta;
4906 AbsDelta := Abs(Delta);
4907
4908 //if Zero offset, just copy any CLOSED polygons to FSolution and return ...
4909 if AbsDelta < Tolerance then
4910 begin
4911 solCount := 0;
4912 SetLength(FSolution, FPolyNodes.ChildCount);
4913 for I := 0 to FPolyNodes.ChildCount -1 do
4914 if FPolyNodes.Childs[I].FEndType = etClosedPolygon then
4915 begin
4916 FSolution[solCount] := FPolyNodes.Childs[I].FPath;
4917 inc(solCount);
4918 end;
4919 SetLength(FSolution, solCount);
4920 Exit;
4921 end;
4922
4923 //FMiterLimit: see offset_triginometry3.svg in the documentation folder ...
4924 if FMiterLimit > 2 then FMiterLim := 2/(sqr(FMiterLimit))
4925 else FMiterLim := 0.5;
4926
4927 if (FArcTolerance <= 0) then Y := def_arc_tolerance
4928 else if FArcTolerance > AbsDelta * def_arc_tolerance then
4929 Y := AbsDelta * def_arc_tolerance
4930 else Y := FArcTolerance;
4931
4932 //see offset_triginometry2.svg in the documentation folder ...
4933 Steps := PI / ArcCos(1 - Y / AbsDelta); //steps per 360 degrees
4934 if (Steps > AbsDelta * Pi) then
4935 Steps := AbsDelta * Pi; //ie excessive precision check
4936
4937 Math.SinCos(Two_Pi / Steps, FSin, FCos); //sin & cos per step
4938 if Delta < 0 then FSin := -FSin;
4939 FStepsPerRad := Steps / Two_Pi;
4940
4941 SetLength(FSolution, FPolyNodes.ChildCount * 2);
4942 solCount := 0;
4943 for I := 0 to FPolyNodes.ChildCount -1 do
4944 begin
4945 Node := FPolyNodes.Childs[I];
4946 FInP := Node.FPath;
4947 Len := length(FInP);
4948
4949 if (Len = 0) or
4950 ((Delta <= 0) and ((Len < 3) or (Node.FEndType <> etClosedPolygon))) then
4951 Continue;
4952
4953 FOutPos := 0;
4954 FOutP := nil;
4955
4956 //if a single vertex then build circle or a square ...
4957 if (Len = 1) then
4958 begin
4959 if Node.FJoinType = jtRound then
4960 begin
4961 X := 1; Y := 0;
4962 for J := 1 to Round(Steps) do
4963 begin
4964 AddPoint(IntPoint(
4965 Round(FInP[0].X + X * FDelta),
4966 Round(FInP[0].Y + Y * FDelta)));
4967 X2 := X;
4968 X := X * FCos - FSin * Y;
4969 Y := X2 * FSin + Y * FCos;
4970 end
4971 end else
4972 begin
4973 X := -1; Y := -1;
4974 for J := 1 to 4 do
4975 begin
4976 AddPoint(IntPoint( Round(FInP[0].X + X * FDelta),
4977 Round(FInP[0].Y + Y * FDelta)));
4978 if X < 0 then X := 1
4979 else if Y < 0 then Y := 1
4980 else X := -1;
4981 end;
4982 end;
4983 SetLength(FOutP, FOutPos);
4984 FSolution[solCount] := FOutP;
4985 Inc(solCount);
4986 Continue;
4987 end;
4988
4989 //build Normals ...
4990 SetLength(FNorms, Len);
4991 for J := 0 to Len-2 do
4992 FNorms[J] := GetUnitNormal(FInP[J], FInP[J+1]);
4993 if not (Node.FEndType in [etClosedLine, etClosedPolygon]) then
4994 FNorms[Len-1] := FNorms[Len-2] else
4995 FNorms[Len-1] := GetUnitNormal(FInP[Len-1], FInP[0]);
4996
4997 if Node.FEndType = etClosedPolygon then
4998 begin
4999 K := Len -1;
5000 for J := 0 to Len-1 do
5001 OffsetPoint(J, K, Node.FJoinType);
5002 SetLength(FOutP, FOutPos);
5003 FSolution[solCount] := FOutP;
5004 Inc(solCount);
5005 end
5006 else if (Node.FEndType = etClosedLine) then
5007 begin
5008 K := Len -1;
5009 for J := 0 to Len-1 do
5010 OffsetPoint(J, K, Node.FJoinType);
5011 SetLength(FOutP, FOutPos);
5012 FSolution[solCount] := FOutP;
5013 Inc(solCount);
5014
5015 FOutPos := 0;
5016 FOutP := nil;
5017
5018 //re-build Normals ...
5019 N := FNorms[Len - 1];
5020 for J := Len-1 downto 1 do
5021 begin
5022 FNorms[J].X := -FNorms[J-1].X;
5023 FNorms[J].Y := -FNorms[J-1].Y;
5024 end;
5025 FNorms[0].X := -N.X;
5026 FNorms[0].Y := -N.Y;
5027
5028 K := 0;
5029 for J := Len-1 downto 0 do
5030 OffsetPoint(J, K, Node.FJoinType);
5031 SetLength(FOutP, FOutPos);
5032
5033 FSolution[solCount] := FOutP;
5034 Inc(solCount);
5035 end else
5036 begin
5037 //offset the polyline going forward ...
5038 K := 0;
5039 for J := 1 to Len-2 do
5040 OffsetPoint(J, K, Node.FJoinType);
5041
5042 //handle the end (butt, round or square) ...
5043 if Node.FEndType = etOpenButt then
5044 begin
5045 J := Len - 1;
5046 AddPoint(IntPoint(round(FInP[J].X + FNorms[J].X *FDelta),
5047 round(FInP[J].Y + FNorms[J].Y * FDelta)));
5048 AddPoint(IntPoint(round(FInP[J].X - FNorms[J].X *FDelta),
5049 round(FInP[J].Y - FNorms[J].Y * FDelta)));
5050 end else
5051 begin
5052 J := Len - 1;
5053 K := Len - 2;
5054 FNorms[J].X := -FNorms[J].X;
5055 FNorms[J].Y := -FNorms[J].Y;
5056 FSinA := 0;
5057 if Node.FEndType = etOpenSquare then
5058 DoSquare(J, K) else
5059 DoRound(J, K);
5060 end;
5061
5062 //re-build Normals ...
5063 for J := Len-1 downto 1 do
5064 begin
5065 FNorms[J].X := -FNorms[J-1].X;
5066 FNorms[J].Y := -FNorms[J-1].Y;
5067 end;
5068 FNorms[0].X := -FNorms[1].X;
5069 FNorms[0].Y := -FNorms[1].Y;
5070
5071 //offset the polyline going backward ...
5072 K := Len -1;
5073 for J := Len -2 downto 1 do
5074 OffsetPoint(J, K, Node.FJoinType);
5075
5076 //finally handle the start (butt, round or square) ...
5077 if Node.FEndType = etOpenButt then
5078 begin
5079 AddPoint(IntPoint(round(FInP[0].X - FNorms[0].X *FDelta),
5080 round(FInP[0].Y - FNorms[0].Y * FDelta)));
5081 AddPoint(IntPoint(round(FInP[0].X + FNorms[0].X *FDelta),
5082 round(FInP[0].Y + FNorms[0].Y * FDelta)));
5083 end else
5084 begin
5085 FSinA := 0;
5086 if Node.FEndType = etOpenSquare then
5087 DoSquare(0, 1) else
5088 DoRound(0, 1);
5089 end;
5090 SetLength(FOutP, FOutPos);
5091 FSolution[solCount] := FOutP;
5092 Inc(solCount);
5093 end;
5094 end;
5095 SetLength(FSolution, solCount);
5096 end;
5097 //------------------------------------------------------------------------------
5098
5099 procedure TClipperOffset.Execute(out solution: TPaths; Delta: Double);
5100 var
5101 I, Len: Integer;
5102 Outer: TPath;
5103 Bounds: TIntRect;
5104 begin
5105 FixOrientations;
5106 DoOffset(Delta);
5107 //now clean up 'corners' ...
5108 with TClipper.Create do
5109 try
5110 AddPaths(FSolution, ptSubject, True);
5111 if Delta > 0 then
5112 begin
5113 Execute(ctUnion, solution, pftPositive, pftPositive);
5114 end else
5115 begin
5116 Bounds := GetBounds(FSolution);
5117 SetLength(Outer, 4);
5118 Outer[0] := IntPoint(Bounds.left-10, Bounds.bottom+10);
5119 Outer[1] := IntPoint(Bounds.right+10, Bounds.bottom+10);
5120 Outer[2] := IntPoint(Bounds.right+10, Bounds.top-10);
5121 Outer[3] := IntPoint(Bounds.left-10, Bounds.top-10);
5122 AddPath(Outer, ptSubject, True);
5123 ReverseSolution := True;
5124 Execute(ctUnion, solution, pftNegative, pftNegative);
5125 //delete the outer rectangle ...
5126 Len := length(solution);
5127 for I := 1 to Len -1 do solution[I-1] := solution[I];
5128 if Len > 0 then SetLength(solution, Len -1);
5129 end;
5130 finally
5131 free;
5132 end;
5133 end;
5134 //------------------------------------------------------------------------------
5135
5136 procedure TClipperOffset.Execute(out solution: TPolyTree; Delta: Double);
5137 var
5138 I: Integer;
5139 Outer: TPath;
5140 Bounds: TIntRect;
5141 OuterNode: TPolyNode;
5142 begin
5143 if not assigned(solution) then
5144 raise exception.Create(rsClipperOffset);
5145 solution.Clear;
5146
5147 FixOrientations;
5148 DoOffset(Delta);
5149
5150 //now clean up 'corners' ...
5151 with TClipper.Create do
5152 try
5153 AddPaths(FSolution, ptSubject, True);
5154 if Delta > 0 then
5155 begin
5156 Execute(ctUnion, solution, pftPositive, pftPositive);
5157 end else
5158 begin
5159 Bounds := GetBounds(FSolution);
5160 SetLength(Outer, 4);
5161 Outer[0] := IntPoint(Bounds.left-10, Bounds.bottom+10);
5162 Outer[1] := IntPoint(Bounds.right+10, Bounds.bottom+10);
5163 Outer[2] := IntPoint(Bounds.right+10, Bounds.top-10);
5164 Outer[3] := IntPoint(Bounds.left-10, Bounds.top-10);
5165 AddPath(Outer, ptSubject, True);
5166 ReverseSolution := True;
5167 Execute(ctUnion, solution, pftNegative, pftNegative);
5168 //remove the outer PolyNode rectangle ...
5169 if (solution.ChildCount = 1) and (solution.Childs[0].ChildCount > 0) then
5170 begin
5171 OuterNode := solution.Childs[0];
5172 SetLength(solution.FChilds, OuterNode.ChildCount);
5173 solution.FChilds[0] := OuterNode.Childs[0];
5174 solution.FChilds[0].FParent := solution;
5175 for I := 1 to OuterNode.ChildCount -1 do
5176 solution.AddChild(OuterNode.Childs[I]);
5177 end else
5178 solution.Clear;
5179 end;
5180 finally
5181 free;
5182 end;
5183 end;
5184 //------------------------------------------------------------------------------
5185
5186 procedure TClipperOffset.AddPoint(const Pt: TIntPoint);
5187 const
5188 BuffLength = 32;
5189 begin
5190 if FOutPos = length(FOutP) then
5191 SetLength(FOutP, FOutPos + BuffLength);
5192 FOutP[FOutPos] := Pt;
5193 Inc(FOutPos);
5194 end;
5195 //------------------------------------------------------------------------------
5196
5197 procedure TClipperOffset.DoSquare(J, K: Integer);
5198 begin
5199 AddPoint(IntPoint(
5200 round(FInP[J].X + FDelta * (FNorms[K].X - FNorms[K].Y)),
5201 round(FInP[J].Y + FDelta * (FNorms[K].Y + FNorms[K].X))));
5202 AddPoint(IntPoint(
5203 round(FInP[J].X + FDelta * (FNorms[J].X + FNorms[J].Y)),
5204 round(FInP[J].Y + FDelta * (FNorms[J].Y - FNorms[J].X))));
5205 end;
5206 //------------------------------------------------------------------------------
5207
5208 procedure TClipperOffset.DoMiter(J, K: Integer; R: Double);
5209 var
5210 Q: Double;
5211 begin
5212 Q := FDelta / R;
5213 AddPoint(IntPoint(round(FInP[J].X + (FNorms[K].X + FNorms[J].X)*Q),
5214 round(FInP[J].Y + (FNorms[K].Y + FNorms[J].Y)*Q)));
5215 end;
5216 //------------------------------------------------------------------------------
5217
5218 procedure TClipperOffset.DoRound(J, K: Integer);
5219 var
5220 I, Steps: Integer;
5221 A, X, X2, Y: Double;
5222 begin
5223 A := ArcTan2(FSinA, FNorms[K].X * FNorms[J].X + FNorms[K].Y * FNorms[J].Y);
5224 Steps := Max(Round(FStepsPerRad * Abs(A)), 1);
5225
5226 X := FNorms[K].X;
5227 Y := FNorms[K].Y;
5228 for I := 1 to Steps do
5229 begin
5230 AddPoint(IntPoint(
5231 round(FInP[J].X + X * FDelta),
5232 round(FInP[J].Y + Y * FDelta)));
5233 X2 := X;
5234 X := X * FCos - FSin * Y;
5235 Y := X2 * FSin + Y * FCos;
5236 end;
5237 AddPoint(IntPoint(
5238 round(FInP[J].X + FNorms[J].X * FDelta),
5239 round(FInP[J].Y + FNorms[J].Y * FDelta)));
5240 end;
5241 //------------------------------------------------------------------------------
5242
5243 procedure TClipperOffset.OffsetPoint(J: Integer;
5244 var K: Integer; JoinType: TJoinType);
5245 var
5246 R, cosA: Double;
5247 begin
5248 //cross product ...
5249 FSinA := (FNorms[K].X * FNorms[J].Y - FNorms[J].X * FNorms[K].Y);
5250 if (Abs(FSinA * FDelta) < 1.0) then
5251 begin
5252 //very nearly collinear edges can occasionally cause tiny self-intersections
5253 //due to rounding so offset with a single vertex here. (nb: The two offset
5254 //vertices that would otherwise have been used would be < 1 unit apart.)
5255 //dot product ...
5256 cosA := (FNorms[K].X * FNorms[J].X + FNorms[J].Y * FNorms[K].Y );
5257 if (cosA > 0) then // angle => 0 deg.
5258 begin
5259 AddPoint(IntPoint(round(FInP[J].X + FNorms[K].X * FDelta),
5260 round(FInP[J].Y + FNorms[K].Y * FDelta)));
5261 Exit;
5262 end
5263 //else angle => 180 deg.
5264 end
5265 else if (FSinA > 1.0) then FSinA := 1.0
5266 else if (FSinA < -1.0) then FSinA := -1.0;
5267
5268 if FSinA * FDelta < 0 then
5269 begin
5270 AddPoint(IntPoint(round(FInP[J].X + FNorms[K].X * FDelta),
5271 round(FInP[J].Y + FNorms[K].Y * FDelta)));
5272 AddPoint(FInP[J]);
5273 AddPoint(IntPoint(round(FInP[J].X + FNorms[J].X * FDelta),
5274 round(FInP[J].Y + FNorms[J].Y * FDelta)));
5275 end
5276 else
5277 case JoinType of
5278 jtMiter:
5279 begin
5280 R := 1 + (FNorms[J].X * FNorms[K].X + FNorms[J].Y * FNorms[K].Y);
5281 if (R >= FMiterLim) then DoMiter(J, K, R)
5282 else DoSquare(J, K);
5283 end;
5284 jtSquare: DoSquare(J, K);
5285 jtRound: DoRound(J, K);
5286 end;
5287 K := J;
5288 end;
5289 //------------------------------------------------------------------------------
5290
5291 function SimplifyPolygon(const Poly: TPath; FillType: TPolyFillType = pftEvenOdd): TPaths;
5292 begin
5293 with TClipper.Create do
5294 try
5295 StrictlySimple := True;
5296 AddPath(Poly, ptSubject, True);
5297 Execute(ctUnion, Result, FillType, FillType);
5298 finally
5299 free;
5300 end;
5301 end;
5302 //------------------------------------------------------------------------------
5303
5304 function SimplifyPolygons(const Polys: TPaths; FillType: TPolyFillType = pftEvenOdd): TPaths;
5305 begin
5306 with TClipper.Create do
5307 try
5308 StrictlySimple := True;
5309 AddPaths(Polys, ptSubject, True);
5310 Execute(ctUnion, Result, FillType, FillType);
5311 finally
5312 free;
5313 end;
5314 end;
5315 //------------------------------------------------------------------------------
5316
5317 function DistanceSqrd(const Pt1, Pt2: TIntPoint): Double;
5318 {$IFDEF INLINING} inline; {$ENDIF}
5319 var
5320 dx, dy: Double;
5321 begin
5322 dx := (Pt1.X - Pt2.X);
5323 dy := (Pt1.Y - Pt2.Y);
5324 result := (dx*dx + dy*dy);
5325 end;
5326 //------------------------------------------------------------------------------
5327
5328 function DistanceFromLineSqrd(const pt, ln1, ln2: TIntPoint): double;
5329 var
5330 A, B, C: double;
5331 begin
5332 //The equation of a line in general form (Ax + By + C = 0)
5333 //given 2 points (x�,y�) & (x�,y�) is ...
5334 //(y� - y�)x + (x� - x�)y + (y� - y�)x� - (x� - x�)y� = 0
5335 //A = (y� - y�); B = (x� - x�); C = (y� - y�)x� - (x� - x�)y�
5336 //perpendicular distance of point (x�,y�) = (Ax� + By� + C)/Sqrt(A� + B�)
5337 //see http://en.wikipedia.org/wiki/Perpendicular_distance
5338 A := ln1.Y - ln2.Y;
5339 B := ln2.X - ln1.X;
5340 C := A * ln1.X + B * ln1.Y;
5341 C := A * pt.X + B * pt.Y - C;
5342 Result := (C * C) / (A * A + B * B);
5343 end;
5344 //---------------------------------------------------------------------------
5345
5346 function SlopesNearCollinear(const Pt1, Pt2, Pt3: TIntPoint;
5347 DistSqrd: Double): Boolean;
5348 begin
5349 //this function is more accurate when the point that's geometrically
5350 //between the other 2 points is the one that's tested for distance.
5351 //ie makes it more likely to pick up 'spikes' ...
5352 if Abs(Pt1.X - Pt2.X) > Abs(Pt1.Y - Pt2.Y) then
5353 begin
5354 if (Pt1.X > Pt2.X) = (Pt1.X < Pt3.X) then
5355 result := DistanceFromLineSqrd(Pt1, Pt2, Pt3) < DistSqrd
5356 else if (Pt2.X > Pt1.X) = (Pt2.X < Pt3.X) then
5357 result := DistanceFromLineSqrd(Pt2, Pt1, Pt3) < DistSqrd
5358 else
5359 result := DistanceFromLineSqrd(Pt3, Pt1, Pt2) < DistSqrd;
5360 end else
5361 begin
5362 if (Pt1.Y > Pt2.Y) = (Pt1.Y < Pt3.Y) then
5363 result := DistanceFromLineSqrd(Pt1, Pt2, Pt3) < DistSqrd
5364 else if (Pt2.Y > Pt1.Y) = (Pt2.Y < Pt3.Y) then
5365 result := DistanceFromLineSqrd(Pt2, Pt1, Pt3) < DistSqrd
5366 else
5367 result := DistanceFromLineSqrd(Pt3, Pt1, Pt2) < DistSqrd;
5368 end;
5369 end;
5370 //------------------------------------------------------------------------------
5371
5372 function PointsAreClose(const Pt1, Pt2: TIntPoint;
5373 DistSqrd: Double): Boolean;
5374 begin
5375 result := DistanceSqrd(Pt1, Pt2) <= DistSqrd;
5376 end;
5377 //------------------------------------------------------------------------------
5378
5379 function CleanPolygon(const Poly: TPath; Distance: Double = 1.415): TPath;
5380 var
5381 I, Len: Integer;
5382 DistSqrd: double;
5383 OutPts: array of TOutPt;
5384 op: POutPt;
5385
5386 function ExcludeOp(op: POutPt): POutPt;
5387 begin
5388 Result := op.Prev;
5389 Result.Next := op.Next;
5390 op.Next.Prev := Result;
5391 Result.Idx := 0;
5392 end;
5393
5394 begin
5395 //Distance = proximity in units/pixels below which vertices
5396 //will be stripped. Default ~= sqrt(2) so when adjacent
5397 //vertices have both x & y coords within 1 unit, then
5398 //the second vertex will be stripped.
5399 DistSqrd := Round(Distance * Distance);
5400 Result := nil;
5401 Len := Length(Poly);
5402 if Len = 0 then Exit;
5403
5404 SetLength(OutPts, Len);
5405 for I := 0 to Len -1 do
5406 begin
5407 OutPts[I].Pt := Poly[I];
5408 OutPts[I].Next := @OutPts[(I + 1) mod Len];
5409 OutPts[I].Next.Prev := @OutPts[I];
5410 OutPts[I].Idx := 0;
5411 end;
5412
5413 op := @OutPts[0];
5414 while (op.Idx = 0) and (op.Next <> op.Prev) do
5415 begin
5416 if PointsAreClose(op.Pt, op.Prev.Pt, DistSqrd) then
5417 begin
5418 op := ExcludeOp(op);
5419 Dec(Len);
5420 end else if PointsAreClose(op.Prev.Pt, op.Next.Pt, DistSqrd) then
5421 begin
5422 ExcludeOp(op.Next);
5423 op := ExcludeOp(op);
5424 Dec(Len, 2);
5425 end
5426 else if SlopesNearCollinear(op.Prev.Pt, op.Pt, op.Next.Pt, DistSqrd) then
5427 begin
5428 op := ExcludeOp(op);
5429 Dec(Len);
5430 end
5431 else
5432 begin
5433 op.Idx := 1;
5434 op := op.Next;
5435 end;
5436 end;
5437
5438 if Len < 3 then Len := 0;
5439 SetLength(Result, Len);
5440 for I := 0 to Len -1 do
5441 begin
5442 Result[I] := op.Pt;
5443 op := op.Next;
5444 end;
5445 end;
5446 //------------------------------------------------------------------------------
5447
5448 function CleanPolygons(const Polys: TPaths; Distance: double = 1.415): TPaths;
5449 var
5450 I, Len: Integer;
5451 begin
5452 Len := Length(Polys);
5453 SetLength(Result, Len);
5454 for I := 0 to Len - 1 do
5455 Result[I] := CleanPolygon(Polys[I], Distance);
5456 end;
5457 //------------------------------------------------------------------------------
5458
5459 function Minkowski(const Base, Path: TPath;
5460 IsSum: Boolean; IsClosed: Boolean): TPaths;
5461 var
5462 i, j, delta, baseLen, pathLen: integer;
5463 quad: TPath;
5464 tmp: TPaths;
5465 begin
5466 if IsClosed then delta := 1 else delta := 0;
5467
5468 baseLen := Length(Base);
5469 pathLen := Length(Path);
5470 setLength(tmp, pathLen);
5471 if IsSum then
5472 for i := 0 to pathLen -1 do
5473 begin
5474 setLength(tmp[i], baseLen);
5475 for j := 0 to baseLen -1 do
5476 begin
5477 tmp[i][j].X := Path[i].X + Base[j].X;
5478 tmp[i][j].Y := Path[i].Y + Base[j].Y;
5479 end;
5480 end
5481 else
5482 for i := 0 to pathLen -1 do
5483 begin
5484 setLength(tmp[i], baseLen);
5485 for j := 0 to baseLen -1 do
5486 begin
5487 tmp[i][j].X := Path[i].X - Base[j].X;
5488 tmp[i][j].Y := Path[i].Y - Base[j].Y;
5489 end;
5490 end;
5491
5492 SetLength(quad, 4);
5493 SetLength(Result, (pathLen + delta) * (baseLen + 1));
5494 for i := 0 to pathLen - 2 + delta do
5495 begin
5496 for j := 0 to baseLen - 1 do
5497 begin
5498 quad[0] := tmp[i mod pathLen][j mod baseLen];
5499 quad[1] := tmp[(i+1) mod pathLen][j mod baseLen];
5500 quad[2] := tmp[(i+1) mod pathLen][(j+1) mod baseLen];
5501 quad[3] := tmp[i mod pathLen][(j+1) mod baseLen];
5502 if not Orientation(quad) then quad := ReversePath(quad);
5503 Result[i*baseLen + j] := copy(quad, 0, 4);
5504 end;
5505 end;
5506 end;
5507 //------------------------------------------------------------------------------
5508
5509 function TranslatePath(const Path: TPath; const Delta: TIntPoint): TPath;
5510 var
5511 i, len: Integer;
5512 begin
5513 len := Length(Path);
5514 SetLength(Result, len);
5515 for i := 0 to High(Path) do
5516 begin
5517 Result[i].X := Path[i].X + Delta.X;
5518 Result[i].Y := Path[i].Y + Delta.Y;
5519 end;
5520 end;
5521 //------------------------------------------------------------------------------
5522
5523 function MinkowskiSum(const Pattern, Path: TPath; PathIsClosed: Boolean): TPaths;
5524 begin
5525 Result := Minkowski(Pattern, Path, true, PathIsClosed);
5526 with TClipper.Create() do
5527 try
5528 AddPaths(Result, ptSubject, True);
5529 Execute(ctUnion, Result, pftNonZero);
5530 finally
5531 Free;
5532 end;
5533 end;
5534 //------------------------------------------------------------------------------
5535
5536 function MinkowskiSum(const Pattern: TPath; const Paths: TPaths;
5537 PathFillType: TPolyFillType; PathIsClosed: Boolean): TPaths;
5538 var
5539 I, Cnt: Integer;
5540 Paths2: TPaths;
5541 Path: TPath;
5542 begin
5543 Result := nil;
5544 if Length(Pattern) = 0 then Exit;
5545 Cnt := Length(Paths);
5546 with TClipper.Create() do
5547 try
5548 for I := 0 to Cnt -1 do
5549 begin
5550 Paths2 := Minkowski(Pattern, Paths[I], true, PathIsClosed);
5551 AddPaths( Paths2, ptSubject, true);
5552 if PathIsClosed then
5553 begin
5554 Path := TranslatePath(Paths[I], Pattern[0]);
5555 AddPath(Path, ptClip, true);
5556 end;
5557 end;
5558 Execute(ctUnion, Result, PathFillType, PathFillType);
5559 finally
5560 Free;
5561 end;
5562 end;
5563 //------------------------------------------------------------------------------
5564
5565 function MinkowskiDiff(const Poly1, Poly2: TPath): TPaths;
5566 begin
5567 Result := Minkowski(Poly1, Poly2, false, true);
5568 with TClipper.Create() do
5569 try
5570 AddPaths(Result, ptSubject, True);
5571 Execute(ctUnion, Result, pftNonZero);
5572 finally
5573 Free;
5574 end;
5575 end;
5576 //------------------------------------------------------------------------------
5577
5578 type
5579 TNodeType = (ntAny, ntOpen, ntClosed);
5580
5581 procedure AddPolyNodeToPaths(PolyNode: TPolyNode;
5582 NodeType: TNodeType; var Paths: TPaths);
5583 var
5584 I: Integer;
5585 Match: Boolean;
5586 begin
5587 case NodeType of
5588 ntAny: Match := True;
5589 ntClosed: Match := not PolyNode.IsOpen;
5590 else Exit;
5591 end;
5592
5593 if (Length(PolyNode.Contour) > 0) and Match then
5594 begin
5595 I := Length(Paths);
5596 SetLength(Paths, I +1);
5597 Paths[I] := PolyNode.Contour;
5598 end;
5599 for I := 0 to PolyNode.ChildCount - 1 do
5600 AddPolyNodeToPaths(PolyNode.Childs[I], NodeType, Paths);
5601 end;
5602 //------------------------------------------------------------------------------
5603
5604 function PolyTreeToPaths(PolyTree: TPolyTree): TPaths;
5605 begin
5606 Result := nil;
5607 AddPolyNodeToPaths(PolyTree, ntAny, Result);
5608 end;
5609 //------------------------------------------------------------------------------
5610
5611 function ClosedPathsFromPolyTree(PolyTree: TPolyTree): TPaths;
5612 begin
5613 Result := nil;
5614 AddPolyNodeToPaths(PolyTree, ntClosed, Result);
5615 end;
5616 //------------------------------------------------------------------------------
5617
5618 function OpenPathsFromPolyTree(PolyTree: TPolyTree): TPaths;
5619 var
5620 I, J: Integer;
5621 begin
5622 Result := nil;
5623 //Open polys are top level only, so ...
5624 for I := 0 to PolyTree.ChildCount - 1 do
5625 if PolyTree.Childs[I].IsOpen then
5626 begin
5627 J := Length(Result);
5628 SetLength(Result, J +1);
5629 Result[J] := PolyTree.Childs[I].Contour;
5630 end;
5631 end;
5632
5633 //------------------------------------------------------------------------------
5634 //------------------------------------------------------------------------------
5635
5636 end.
5637