source: trunk/Packages/Graphics32/GR32_Clipper.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 102.5 KB
Line 
1unit GR32_Clipper;
2
3(* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1 or LGPL 2.1 with linking exception
5 *
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
10 *
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
15 *
16 * Alternatively, the contents of this file may be used under the terms of the
17 * Free Pascal modified version of the GNU Lesser General Public License
18 * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
19 * of this license are applicable instead of those above.
20 * Please see the file LICENSE.txt for additional information concerning this
21 * license.
22 *
23 * The Original Code is GR32_Clipper
24 *
25 * The Initial Developer of the Original Code is
26 * Angus Johnson
27 *
28 * Portions created by the Initial Developer are Copyright (C) 2012-2019
29 * the Initial Developer. All Rights Reserved.
30 *
31 * Contributor(s):
32 *
33 * ***** END LICENSE BLOCK ***** *)
34
35{$IFDEF FPC}
36 {$DEFINE USEINLINING}
37{$ELSE}
38 {$IF CompilerVersion >= 18} // Delphi 2007
39 // While USEINLINING has been supported since D2005, both D2005 and D2006
40 // have an Inline codegen bug (QC41166) so ignore Inline until D2007.
41 {$DEFINE USEINLINING}
42 {$IF CompilerVersion >= 25.0} // Delphi XE4+
43 {$LEGACYIFEND ON}
44 {$IFEND}
45 {$IFEND}
46 {$IF CompilerVersion < 14}
47 Requires Delphi version 6 or above.
48 {$IFEND}
49{$ENDIF}
50
51{$IFDEF DEBUG}
52 {$UNDEF USEINLINING}
53{$ENDIF}
54
55interface
56
57uses
58 Classes, SysUtils, Math, GR32;
59
60type
61 TPoint64 = record X, Y: Int64; end;
62
63 // TPath: a simple data structure to represent a series of vertices, whether
64 // open (poly-line) or closed (polygon). A path may be simple or complex (self
65 // intersecting). For simple polygons, path orientation (whether clockwise or
66 // counterclockwise) is generally used to differentiate outer paths from inner
67 // paths (holes). For complex polygons (and also for overlapping polygons),
68 // explicit 'filling rules' (see below) are used to indicate regions that are
69 // inside (filled) and regions that are outside (unfilled) a specific polygon.
70 TPath = array of TPoint64;
71 TPaths = array of TPath;
72 TArrayOfPaths = array of TPaths;
73
74 TClipType = (ctNone, ctIntersection, ctUnion, ctDifference, ctXor);
75 // Note: all clipping operations except for Difference are commutative.
76 TPathType = (ptSubject, ptClip);
77 TFillRule = (frEvenOdd, frNonZero, frPositive, frNegative);
78
79 EClipperLibException = class(Exception);
80
81 TJoinType = (jtSquare, jtRound, jtRoundEx, jtMiter);
82 TEndType = (etPolygon, etOpenJoined, etOpenButt, etOpenSquare, etOpenRound);
83
84
85 TVertexFlag = (vfOpenStart, vfOpenEnd, vfLocMax, vfLocMin);
86 TVertexFlags = set of TVertexFlag;
87
88 PVertex = ^TVertex;
89 TVertex = record
90 Pt : TPoint64;
91 next : PVertex;
92 prev : PVertex;
93 flags : TVertexFlags;
94 end;
95
96 PVertexArray = ^TVertexArray;
97 TVertexArray = array[0..MaxInt div sizeof(TVertex) -1] of TVertex;
98
99 // Every closed path (or polygon) is made up of a series of vertices forming
100 // edges that alternate between going up (relative to the Y-axis) and going
101 // down. Edges consecutively going up or consecutively going down are called
102 // 'bounds' (or sides if they're simple polygons). 'Local Minima' refer to
103 // vertices where descending bounds become ascending ones.
104
105 PLocalMinima = ^TLocalMinima;
106 TLocalMinima = record
107 vertex : PVertex;
108 PolyType : TPathType;
109 IsOpen : Boolean;
110 end;
111
112 TOutRec = class;
113
114 TOutPt = class
115 Pt : TPoint64;
116 Next : TOutPt;
117 Prev : TOutPt;
118 OutRec : TOutRec; // used in descendant classes
119 end;
120
121 PActive = ^TActive;
122 TActive = record
123 op : TOutPt; // used in descendant classes
124 Bot : TPoint64;
125 Top : TPoint64;
126 CurrX : Int64;
127 Dx : Double; // inverse of edge slope (zero = vertical)
128 WindDx : Integer; // wind direction (ascending: +1; descending: -1)
129 WindCnt : Integer; // current wind count
130 WindCnt2 : Integer; // current wind count of the opposite TPolyType
131 OutRec : TOutRec;
132 // AEL: 'active edge list' (Vatti's AET - active edge table)
133 // a linked list of all edges (from left to right) that are present
134 // (or 'active') within the current scanbeam (a horizontal 'beam' that
135 // sweeps from bottom to top over the paths in the clipping operation).
136 PrevInAEL: PActive;
137 NextInAEL: PActive;
138 // SEL: 'sorted edge list' (Vatti's ST - sorted table)
139 // linked list used when sorting edges into their new positions at the
140 // top of scanbeams, but also (re)used to process horizontals.
141 PrevInSEL: PActive;
142 NextInSEL: PActive;
143 Jump : PActive; // for merge sorting (see BuildIntersectList())
144 VertTop : PVertex;
145 LocMin : PLocalMinima; // the bottom of an edge 'bound' (also Vatti)
146 end;
147
148 PIntersectNode = ^TIntersectNode;
149 TIntersectNode = record
150 Edge1 : PActive;
151 Edge2 : PActive;
152 Pt : TPoint64;
153 end;
154
155 PScanLine = ^TScanLine;
156 TScanLine = record
157 Y : Int64;
158 Next : PScanLine;
159 end;
160
161 TOutRecState = (osUndefined, osOpen, osOuter,
162 osOuterCheck, osInner, osInnerCheck);
163
164 // OutRec: contains a path in the clipping solution. Edges in the AEL will
165 // have OutRec pointers assigned when they form part of the clipping solution.
166 TOutRec = class
167 Idx : Integer;
168 Owner : TOutRec;
169 frontE : PActive;
170 backE : PActive;
171 Pts : TOutPt;
172 State : TOutRecState;
173 end;
174
175 TClipper = class
176 private
177 FBotY : Int64;
178 FScanLine : PScanLine;
179 FLocMinListSorted : Boolean;
180 FHasOpenPaths : Boolean;
181 FCurrentLocMinIdx : Integer;
182 FClipType : TClipType;
183 FFillRule : TFillRule;
184 FIntersectList : TList;
185 FOutRecList : TList;
186 FLocMinList : TList;
187 FActives : PActive; // see AEL above
188 FSel : PActive; // see SEL above
189 FVertexList : TList;
190 procedure Reset;
191 procedure InsertScanLine(const Y: Int64);
192 function PopScanLine(out Y: Int64): Boolean;
193 function PopLocalMinima(Y: Int64;
194 out localMinima: PLocalMinima): Boolean;
195 procedure DisposeScanLineList;
196 procedure DisposeOutRec(index: Integer);
197 procedure DisposeAllOutRecs;
198 procedure DisposeVerticesAndLocalMinima;
199 procedure AddPathToVertexList(const path: TArrayOfFloatPoint;
200 polyType: TPathType; isOpen: Boolean);
201 function IsContributingClosed(e: PActive): Boolean;
202 function IsContributingOpen(e: PActive): Boolean;
203 procedure SetWindCountForClosedPathEdge(e: PActive);
204 procedure SetWindCountForOpenPathEdge(e: PActive);
205 procedure InsertLocalMinimaIntoAEL(const botY: Int64);
206 procedure InsertLeftEdge(e: PActive);
207 procedure PushHorz(e: PActive); {$IFDEF USEINLINING} inline; {$ENDIF}
208 function PopHorz(out e: PActive): Boolean;
209 {$IFDEF USEINLINING} inline; {$ENDIF}
210 procedure StartOpenPath(e: PActive; const pt: TPoint64);
211 procedure UpdateEdgeIntoAEL(var e: PActive);
212 procedure IntersectEdges(e1, e2: PActive;
213 const pt: TPoint64; orientationCheckRequired: Boolean = false);
214 procedure DeleteFromAEL(e: PActive);
215 procedure AdjustCurrXAndCopyToSEL(topY: Int64);
216 procedure DoIntersections(const topY: Int64);
217 procedure DisposeIntersectNodes;
218 procedure AddNewIntersectNode(e1, e2: PActive; topY: Int64);
219 function BuildIntersectList(const topY: Int64): Boolean;
220 procedure ProcessIntersectList;
221 procedure SwapPositionsInAEL(e1, e2: PActive);
222 procedure DoHorizontal(horzEdge: PActive);
223 procedure DoTopOfScanbeam(Y: Int64);
224 function DoMaxima(e: PActive): PActive;
225 function AddOutPt(e: PActive; const pt: TPoint64): TOutPt;
226 procedure AddLocalMinPoly(e1, e2: PActive; const pt: TPoint64;
227 IsNew: Boolean = false; orientationCheckRequired: Boolean = false);
228 procedure AddLocalMaxPoly(e1, e2: PActive; const pt: TPoint64);
229 procedure JoinOutrecPaths(e1, e2: PActive);
230 function GetIntersectNode(index: Integer): PIntersectNode;
231 {$IFDEF USEINLINING} inline; {$ENDIF}
232 protected
233 procedure CleanUp; // unlike Clear, CleanUp preserves added paths
234 procedure ExecuteInternal(clipType: TClipType;
235 fillRule: TFillRule); virtual;
236 function BuildResult(out closedPaths,
237 openPaths: TArrayOfArrayOfFloatPoint): Boolean;
238 property OutRecList: TList read FOutRecList;
239 property IntersectNode[index: Integer]: PIntersectNode
240 read GetIntersectNode;
241 public
242 constructor Create; virtual;
243 destructor Destroy; override;
244 procedure Clear;
245 function GetBounds: TFloatRect;
246
247 // ADDPATH & ADDPATHS METHODS ...
248 procedure AddPath(const path64: TArrayOfFloatPoint;
249 polyType: TPathType = ptSubject; isOpen: Boolean = false); overload;
250 procedure AddPath(const path: TArrayOfFixedPoint;
251 polyType: TPathType = ptSubject; isOpen: Boolean = false); overload;
252 procedure AddPaths(const paths64: TArrayOfArrayOfFloatPoint;
253 polyType: TPathType = ptSubject;
254 isOpen: Boolean = false); overload;
255 procedure AddPaths(const paths: TArrayOfArrayOfFixedPoint;
256 polyType: TPathType = ptSubject;
257 isOpen: Boolean = false); overload;
258
259 // EXECUTE METHODS ...
260 function Execute(clipType: TClipType; fillRule: TFillRule;
261 out closedPaths: TArrayOfArrayOfFloatPoint): Boolean; overload;
262 function Execute(clipType: TClipType; fillRule: TFillRule;
263 out closedPaths: TArrayOfArrayOfFixedPoint): Boolean; overload;
264 function Execute(clipType: TClipType; fillRule: TFillRule;
265 out closedPaths, openPaths: TArrayOfArrayOfFloatPoint): Boolean; overload;
266 function Execute(clipType: TClipType; fillRule: TFillRule;
267 out closedPaths, openPaths: TArrayOfArrayOfFixedPoint): Boolean; overload;
268 end;
269
270 TClipperOffset = class
271 private
272 FDelta: Double;
273 FJoinType: TJoinType;
274 FEndType : TEndType;
275
276 FStepSizeSin, FStepSizeCos: Extended;
277 FMiterLim, FMiterLimit: Double;
278 FStepsPerRad: Double;
279 FArcTolerance: Double;
280
281 FNorms: TArrayOfFloatPoint;
282 FSolution: TArrayOfArrayOfFloatPoint;
283 FSolutionLen: integer;
284
285 FPathsIn: TArrayOfArrayOfFloatPoint;
286 FPathIn: TArrayOfFloatPoint;
287 FPathOut: TArrayOfFloatPoint;
288 FPathOutLen: Integer;
289
290 procedure AddPoint(const pt: TFloatPoint);
291 procedure DoSquare(j, k: Integer);
292 procedure DoMiter(j, k: Integer; cosAplus1: Double);
293 procedure DoRound(j, k: Integer);
294 procedure OffsetPoint(j,k: Integer);
295
296 function CheckPaths: boolean;
297 function GetLowestPolygonIdx: integer;
298 procedure OffsetPaths;
299 procedure BuildNormals;
300 procedure ReverseNormals;
301 procedure OffsetPolygon;
302 procedure OffsetOpenJoined;
303 procedure OffsetOpenPath;
304 public
305 constructor Create(MiterLimit: Double = 2.0; ArcTolerance: Double = 0.0);
306 destructor Destroy; override;
307 procedure AddPath(const path: TArrayOfFloatPoint);
308 procedure AddPaths(const paths: TArrayOfArrayOfFloatPoint);
309 procedure Clear;
310 procedure Execute(delta: Double; jt: TJoinType; et: TEndType;
311 out solution: TArrayOfArrayOfFloatPoint);
312 property MiterLimit: Double read FMiterLimit write FMiterLimit;
313 property ArcTolerance: Double read FArcTolerance write FArcTolerance;
314 end;
315
316 function InflatePaths(const paths: TArrayOfArrayOfFloatPoint;
317 delta: Double; jt: TJoinType; et: TEndType;
318 miterLimit: single = 0): TArrayOfArrayOfFloatPoint;
319
320implementation
321
322const
323 Tolerance : Double = 1.0E-15;
324 DefaultArcFrac : Double = 0.02;
325 Two_Pi : Double = 2 * PI;
326 LowestIp : TPoint64 = (X: High(Int64); Y: High(Int64));
327
328// OVERFLOWCHECKS OFF is a necessary workaround for a compiler bug that very
329// occasionally report incorrect overflow errors in Delphi versions before 10.2.
330// see https://forums.embarcadero.com/message.jspa?messageID=871444
331{$OVERFLOWCHECKS OFF}
332
333resourcestring
334 rsClipper_OpenPathErr = 'Only subject paths can be open.';
335 rsClipper_ClippingErr = 'Undefined clipping error';
336
337//------------------------------------------------------------------------------
338// Miscellaneous Functions ...
339//------------------------------------------------------------------------------
340
341function Point64(const fp: TFloatPoint): TPoint64; overload;
342 {$IFDEF USEINLINING} inline; {$ENDIF}
343begin
344 Result.X := Round(fp.X * FixedOne);
345 Result.Y := Round(fp.Y * FixedOne);
346end;
347//------------------------------------------------------------------------------
348
349function Point64(const X, Y: Int64): TPoint64; overload;
350begin
351 Result.X := X;
352 Result.Y := Y;
353end;
354//------------------------------------------------------------------------------
355
356function FloatPoint(const pt: TPoint64): TFloatPoint; overload;
357 {$IFDEF USEINLINING} inline; {$ENDIF}
358begin
359 Result.X := pt.X * FixedToFloat;
360 Result.Y := pt.Y * FixedToFloat;
361end;
362//------------------------------------------------------------------------------
363
364function FixedToFloat(const fixed: TArrayOfFixedPoint): TArrayOfFloatPoint;
365var
366 i, len: Integer;
367begin
368 len := length(fixed);
369 setLength(Result, len);
370 for i := 0 to len -1 do
371 Result[i] := FloatPoint(fixed[i]);
372end;
373//------------------------------------------------------------------------------
374
375function FloatToFixed(const float: TArrayOfFloatPoint):
376 TArrayOfFixedPoint; overload;
377var
378 i, len: Integer;
379begin
380 len := length(float);
381 setLength(Result, len);
382 for i := 0 to len -1 do
383 Result[i] := FixedPoint(float[i]);
384end;
385//------------------------------------------------------------------------------
386
387function FloatToFixed(const float: TArrayOfArrayOfFloatPoint):
388 TArrayOfArrayOfFixedPoint; overload;
389var
390 i, len: Integer;
391begin
392 len := length(float);
393 setLength(Result, len);
394 for i := 0 to len -1 do
395 Result[i] := FloatToFixed(float[i]);
396end;
397//------------------------------------------------------------------------------
398
399function PointsEqual(const p1, p2: TPoint64): Boolean; overload;
400 {$IFDEF USEINLINING} inline; {$ENDIF}
401begin
402 Result := (p1.X = p2.X) and (p1.Y = p2.Y);
403end;
404//------------------------------------------------------------------------------
405
406function PointsEqual(const p1, p2: TFloatPoint): Boolean; overload;
407 {$IFDEF USEINLINING} inline; {$ENDIF}
408begin
409 Result := (p1.X = p2.X) and (p1.Y = p2.Y);
410end;
411//------------------------------------------------------------------------------
412
413function IsOpen(e: PActive): Boolean; overload;
414 {$IFDEF USEINLINING} inline; {$ENDIF}
415begin
416 Result := e.LocMin.IsOpen;
417end;
418//------------------------------------------------------------------------------
419
420function IsOpen(outrec: TOutRec): Boolean; overload;
421 {$IFDEF USEINLINING} inline; {$ENDIF}
422begin
423 Result := outrec.State = osOpen;
424end;
425//------------------------------------------------------------------------------
426
427function IsOuter(outrec: TOutRec): Boolean;
428 {$IFDEF USEINLINING} inline; {$ENDIF}
429begin
430 Result := outrec.State in [osOuter, osOuterCheck];
431end;
432//------------------------------------------------------------------------------
433
434procedure SetAsOuter(outrec: TOutRec); {$IFDEF USEINLINING} inline; {$ENDIF}
435begin
436 outrec.State := osOuter;
437end;
438//------------------------------------------------------------------------------
439
440function IsInner(outrec: TOutRec): Boolean;
441 {$IFDEF USEINLINING} inline; {$ENDIF}
442begin
443 Result := outrec.State in [osInner, osInnerCheck];
444end;
445//------------------------------------------------------------------------------
446
447procedure SetAsInner(outrec: TOutRec); {$IFDEF USEINLINING} inline; {$ENDIF}
448begin
449 outrec.State := osInner;
450end;
451//------------------------------------------------------------------------------
452
453procedure SetCheckFlag(outrec: TOutRec); {$IFDEF USEINLINING} inline; {$ENDIF}
454begin
455 if outrec.State = osInner then
456 outrec.State := osInnerCheck
457 else if outrec.State = osOuter then
458 outrec.State := osOuterCheck;
459end;
460//------------------------------------------------------------------------------
461
462procedure UnsetCheckFlag(outrec: TOutRec); {$IFDEF USEINLINING} inline; {$ENDIF}
463begin
464 if outrec.State = osInnerCheck then outrec.State := osInner
465 else if outrec.State = osOuterCheck then outrec.State := osOuter;
466end;
467//------------------------------------------------------------------------------
468
469function IsHotEdge(e: PActive): Boolean; {$IFDEF USEINLINING} inline; {$ENDIF}
470begin
471 Result := assigned(e.OutRec);
472end;
473//------------------------------------------------------------------------------
474
475function GetPrevHotEdge(e: PActive): PActive;
476 {$IFDEF USEINLINING} inline; {$ENDIF}
477begin
478 Result := e.PrevInAEL;
479 while assigned(Result) and (IsOpen(Result) or not IsHotEdge(Result)) do
480 Result := Result.PrevInAEL;
481end;
482//------------------------------------------------------------------------------
483
484function IsFront(e: PActive): Boolean; {$IFDEF USEINLINING} inline; {$ENDIF}
485begin
486 // the front edge will be the LEFT edge when it's an OUTER polygon
487 // so that outer polygons will be orientated clockwise
488 Result := (e = e.OutRec.frontE);
489end;
490//------------------------------------------------------------------------------
491
492function IsInvalidPath(op: TOutPt): Boolean;
493 {$IFDEF USEINLINING} inline; {$ENDIF}
494begin
495 Result := not assigned(op) or (op.Next = op);
496end;
497//------------------------------------------------------------------------------
498
499(*******************************************************************************
500* Dx: 0(90deg) *
501* | *
502* +inf (180deg) <--- o ---> -inf (0deg) *
503*******************************************************************************)
504
505function GetDx(const pt1, pt2: TPoint64): Double;
506 {$IFDEF USEINLINING} inline; {$ENDIF}
507var
508 dy: Int64;
509begin
510 dy := (pt2.Y - pt1.Y);
511 if dy <> 0 then Result := (pt2.X - pt1.X) / dy
512 else if (pt2.X > pt1.X) then Result := NegInfinity
513 else Result := Infinity;
514end;
515//------------------------------------------------------------------------------
516
517function TopX(e: PActive; const currentY: Int64): Int64; overload;
518 {$IFDEF USEINLINING} inline; {$ENDIF}
519begin
520 if (currentY = e.Top.Y) or (e.Top.X = e.Bot.X) then Result := e.Top.X
521 else Result := e.Bot.X + Round(e.Dx*(currentY - e.Bot.Y));
522end;
523//------------------------------------------------------------------------------
524
525function TopX(const pt1, pt2: TPoint64; const Y: Int64): Int64; overload;
526 {$IFDEF USEINLINING} inline; {$ENDIF}
527var
528 dx: Double;
529begin
530 if (Y = pt1.Y) then Result := pt1.X
531 else if (Y = pt2.Y) then Result := pt2.X
532 else if (pt1.Y = pt2.Y) or (pt1.X = pt2.X) then Result := pt2.X
533 else
534 begin
535 dx := GetDx(pt1, pt2);
536 Result := pt1.X + Round(dx * (Y - pt1.Y));
537 end;
538end;
539//------------------------------------------------------------------------------
540
541function IsHorizontal(e: PActive): Boolean;
542 {$IFDEF USEINLINING} inline; {$ENDIF}
543begin
544 Result := (e.Top.Y = e.Bot.Y);
545end;
546//------------------------------------------------------------------------------
547
548function IsHeadingRightHorz(e: PActive): Boolean;
549 {$IFDEF USEINLINING} inline; {$ENDIF}
550begin
551 Result := (e.Dx = NegInfinity);
552end;
553//------------------------------------------------------------------------------
554
555function IsHeadingLeftHorz(e: PActive): Boolean;
556 {$IFDEF USEINLINING} inline; {$ENDIF}
557begin
558 Result := (e.Dx = Infinity);
559end;
560//------------------------------------------------------------------------------
561
562procedure SwapActives(var e1, e2: PActive);
563 {$IFDEF USEINLINING} inline; {$ENDIF}
564var
565 e: PActive;
566begin
567 e := e1; e1 := e2; e2 := e;
568end;
569//------------------------------------------------------------------------------
570
571function GetPolyType(const e: PActive): TPathType;
572 {$IFDEF USEINLINING} inline; {$ENDIF}
573begin
574 Result := e.LocMin.PolyType;
575end;
576//------------------------------------------------------------------------------
577
578function IsSamePolyType(const e1, e2: PActive): Boolean;
579 {$IFDEF USEINLINING} inline; {$ENDIF}
580begin
581 Result := e1.LocMin.PolyType = e2.LocMin.PolyType;
582end;
583//------------------------------------------------------------------------------
584
585function GetIntersectPoint(e1, e2: PActive): TPoint64;
586var
587 b1, b2, m: Double;
588begin
589 if (e1.Dx = e2.Dx) then
590 begin
591 Result := e1.Top;
592 Exit;
593 end
594 else if e1.Dx = 0 then
595 begin
596 Result.X := e1.Bot.X;
597 if IsHorizontal(e2) then
598 Result.Y := e2.Bot.Y
599 else
600 begin
601 with e2^ do b2 := Bot.Y - (Bot.X/Dx);
602 Result.Y := round(Result.X/e2.Dx + b2);
603 end;
604 end
605 else if e2.Dx = 0 then
606 begin
607 Result.X := e2.Bot.X;
608 if IsHorizontal(e1) then
609 Result.Y := e1.Bot.Y
610 else
611 begin
612 with e1^ do b1 := Bot.Y - (Bot.X/Dx);
613 Result.Y := round(Result.X/e1.Dx + b1);
614 end;
615 end else
616 begin
617 with e1^ do b1 := Bot.X - Bot.Y * Dx;
618 with e2^ do b2 := Bot.X - Bot.Y * Dx;
619 m := (b2-b1)/(e1.Dx - e2.Dx);
620 Result.Y := round(m);
621 if Abs(e1.Dx) < Abs(e2.Dx) then
622 Result.X := round(e1.Dx * m + b1) else
623 Result.X := round(e2.Dx * m + b2);
624 end;
625end;
626//------------------------------------------------------------------------------
627
628procedure SetDx(e: PActive); {$IFDEF USEINLINING} inline; {$ENDIF}
629begin
630 e.Dx := GetDx(e.Bot, e.Top);
631end;
632//------------------------------------------------------------------------------
633
634function IsLeftBound(e: PActive): Boolean; {$IFDEF USEINLINING} inline; {$ENDIF}
635begin
636 Result := e.WindDx > 0;
637end;
638//------------------------------------------------------------------------------
639
640function NextVertex(e: PActive): PVertex; overload;
641 {$IFDEF USEINLINING} inline; {$ENDIF}
642begin
643 if IsLeftBound(e) then
644 Result := e.vertTop.next else
645 Result := e.vertTop.prev;
646end;
647//------------------------------------------------------------------------------
648
649function NextVertex(op: PVertex; goingFwd: Boolean): PVertex; overload;
650 {$IFDEF USEINLINING} inline; {$ENDIF}
651begin
652 if goingFwd then Result := op.next
653 else Result := op.prev;
654end;
655//------------------------------------------------------------------------------
656
657function PrevVertex(op: PVertex; goingFwd: Boolean): PVertex;
658 {$IFDEF USEINLINING} inline; {$ENDIF}
659begin
660 if goingFwd then Result := op.prev
661 else Result := op.next;
662end;
663//------------------------------------------------------------------------------
664
665function CrossProduct(const pt1, pt2, pt3: TPoint64): Double;
666var
667 x1,x2,y1,y2: Double;
668begin
669 x1 := pt2.X - pt1.X;
670 y1 := pt2.Y - pt1.Y;
671 x2 := pt3.X - pt2.X;
672 y2 := pt3.Y - pt2.Y;
673 Result := (x1 * y2 - y1 * x2);
674end;
675//---------------------------------------------------------------------------
676
677function IsClockwise(vertex: PVertex): Boolean; overload;
678 {$IFDEF USEINLINING} inline; {$ENDIF}
679begin
680 Result := CrossProduct(vertex.prev.Pt, vertex.Pt, vertex.next.Pt) >= 0;
681end;
682//----------------------------------------------------------------------
683
684function IsClockwise(op: TOutPt): Boolean; overload;
685 {$IFDEF USEINLINING} inline; {$ENDIF}
686begin
687 Result := CrossProduct(op.prev.Pt, op.Pt, op.next.Pt) >= 0;
688end;
689//----------------------------------------------------------------------
690
691function IsMaxima(e: PActive): Boolean; {$IFDEF USEINLINING} inline; {$ENDIF}
692begin
693 Result := vfLocMax in e.vertTop.flags;
694end;
695//------------------------------------------------------------------------------
696
697procedure TerminateHotOpen(e: PActive);
698begin
699 if e.OutRec.frontE = e then
700 e.OutRec.frontE := nil else
701 e.OutRec.backE := nil;
702 e.OutRec := nil;
703end;
704//------------------------------------------------------------------------------
705
706function GetMaximaPair(e: PActive): PActive;
707begin
708 if IsHorizontal(e) then
709 begin
710 // we can't be sure whether the MaximaPair is on the left or right, so ...
711 Result := e.PrevInAEL;
712 while assigned(Result) and (Result.CurrX >= e.Top.X) do
713 begin
714 if Result.vertTop = e.vertTop then Exit; // Found!
715 Result := Result.PrevInAEL;
716 end;
717 Result := e.NextInAEL;
718 while assigned(Result) and (TopX(Result, e.Top.Y) <= e.Top.X) do
719 begin
720 if Result.vertTop = e.vertTop then Exit; // Found!
721 Result := Result.NextInAEL;
722 end;
723 end else
724 begin
725 Result := e.NextInAEL;
726 while assigned(Result) do
727 begin
728 if Result.vertTop = e.vertTop then Exit; // Found!
729 Result := Result.NextInAEL;
730 end;
731 end;
732 Result := nil;
733end;
734//------------------------------------------------------------------------------
735
736function PointCount(pts: TOutPt): Integer; {$IFDEF USEINLINING} inline; {$ENDIF}
737var
738 p: TOutPt;
739begin
740 Result := 0;
741 if not Assigned(pts) then Exit;
742 p := pts;
743 repeat
744 Inc(Result);
745 p := p.Next;
746 until p = pts;
747end;
748//------------------------------------------------------------------------------
749
750function BuildPath(op: TOutPt): TArrayOfFloatPoint;
751var
752 i,j, opCnt: Integer;
753begin
754 Result := nil;
755 opCnt := PointCount(op);
756 if (opCnt < 2) then Exit;
757 setLength(Result, opCnt);
758 Result[0] := FloatPoint(op.Pt);
759 op := op.Next;
760 j := 1;
761 for i := 0 to opCnt -2 do
762 begin
763 Result[j] := FloatPoint(op.Pt);
764 if not PointsEqual(Result[j], Result[j-1]) then inc(j);
765 op := op.Next;
766 end;
767 setLength(Result, j);
768end;
769//------------------------------------------------------------------------------
770
771procedure DisposeOutPt(pp: TOutPt); {$IFDEF USEINLINING} inline; {$ENDIF}
772begin
773 pp.Prev.Next := pp.Next;
774 pp.Next.Prev := pp.Prev;
775 pp.Free;
776end;
777//------------------------------------------------------------------------------
778
779procedure DisposePolyPts(pp: TOutPt); {$IFDEF USEINLINING} inline; {$ENDIF}
780var
781 tmpPp: TOutPt;
782begin
783 pp.Prev.Next := nil;
784 while Assigned(pp) do
785 begin
786 tmpPp := pp;
787 pp := pp.Next;
788 tmpPp.Free;
789 end;
790end;
791//------------------------------------------------------------------------------
792
793function LocMinListSort(item1, item2: Pointer): Integer;
794var
795 dy: Int64;
796begin
797 dy := PLocalMinima(item2).vertex.Pt.Y - PLocalMinima(item1).vertex.Pt.Y;
798 if dy < 0 then Result := -1
799 else if dy > 0 then Result := 1
800 else Result := 0;
801end;
802//------------------------------------------------------------------------------
803
804procedure SetSides(outRec: TOutRec; startEdge, endEdge: PActive);
805 {$IFDEF USEINLINING} inline; {$ENDIF}
806begin
807 outRec.frontE := startEdge;
808 outRec.backE := endEdge;
809end;
810//------------------------------------------------------------------------------
811
812procedure SwapOutRecs(e1, e2: PActive);
813var
814 or1, or2: TOutRec;
815 e: PActive;
816begin
817 or1 := e1.OutRec;
818 or2 := e2.OutRec;
819 if (or1 = or2) then
820 begin
821 e := or1.frontE;
822 or1.frontE := or1.backE;
823 or1.backE := e;
824 Exit;
825 end;
826 if assigned(or1) then
827 begin
828 if e1 = or1.frontE then
829 or1.frontE := e2 else
830 or1.backE := e2;
831 end;
832 if assigned(or2) then
833 begin
834 if e2 = or2.frontE then
835 or2.frontE := e1 else
836 or2.backE := e1;
837 end;
838 e1.OutRec := or2;
839 e2.OutRec := or1;
840end;
841//------------------------------------------------------------------------------
842
843function Area(const path: TArrayOfFloatPoint): Double; overload;
844var
845 i, j, highI: Integer;
846 d: Double;
847begin
848 Result := 0.0;
849 highI := High(path);
850 if (highI < 2) then Exit;
851 j := highI;
852 for i := 0 to highI do
853 begin
854 d := (path[j].X + path[i].X);
855 Result := Result + d * (path[j].Y - path[i].Y);
856 j := i;
857 end;
858 Result := -Result * 0.5;
859end;
860//------------------------------------------------------------------------------
861
862function Area(op: TOutPt): Double; overload;
863var
864 op2: TOutPt;
865 d: Double;
866begin
867 // positive results are clockwise
868 Result := 0;
869 op2 := op;
870 if Assigned(op2) then
871 repeat
872 d := op2.Prev.Pt.X + op2.Pt.X;
873 Result := Result + d * (op2.Prev.Pt.Y - op2.Pt.Y);
874 op2 := op2.Next;
875 until op2 = op;
876 Result := Result * -0.5;
877end;
878//------------------------------------------------------------------------------
879
880procedure ReverseOutPts(op: TOutPt);
881var
882 op1, op2: TOutPt;
883begin
884 if not Assigned(op) then Exit;
885 op1 := op;
886 repeat
887 op2:= op1.Next;
888 op1.Next := op1.Prev;
889 op1.Prev := op2;
890 op1 := op2;
891 until op1 = op;
892end;
893//------------------------------------------------------------------------------
894
895function RecheckInnerOuter(e: PActive): Boolean;
896var
897 wasOuter, isOuter: Boolean;
898 e2: PActive;
899 area: Double;
900begin
901 area := GR32_Clipper.Area(e.outrec.Pts);
902 Result := area <> 0;
903 if not Result then Exit; // returns false when area == 0
904
905 wasOuter := GR32_Clipper.IsOuter(e.OutRec);
906 isOuter := true;
907 e2 := e.PrevInAEL;
908 while assigned(e2) do
909 begin
910 if IsHotEdge(e2) and not IsOpen(e2) then isOuter := not isOuter;
911 e2 := e2.PrevInAEL;
912 end;
913
914 if isOuter <> wasOuter then
915 begin
916 if isOuter then SetAsOuter(e.outrec)
917 else SetAsInner(e.outrec);
918 end;
919
920 e2 := GetPrevHotEdge(e);
921 if isOuter then
922 begin
923 if assigned(e2) and IsInner(e2.OutRec) then e.OutRec.Owner := e2.OutRec
924 else e.OutRec.Owner := nil;
925 end else
926 begin
927 if not assigned(e2) then SetAsOuter(e.OutRec)
928 else if IsInner(e2.OutRec) then e.OutRec.Owner := e2.OutRec.Owner
929 else e.OutRec.Owner := e2.OutRec;
930 end;
931
932 if (area > 0) <> isOuter then ReverseOutPts(e.outrec.Pts);
933 UnsetCheckFlag(e.OutRec);
934end;
935//------------------------------------------------------------------------------
936
937procedure SwapSides(outRec: TOutRec); {$IFDEF USEINLINING} inline; {$ENDIF}
938var
939 e2: PActive;
940begin
941 e2 := outRec.frontE;
942 outRec.frontE := outRec.backE;
943 outRec.backE := e2;
944 outRec.Pts := outRec.Pts.Next;
945end;
946//------------------------------------------------------------------------------
947
948function FixSides(e: PActive): Boolean;
949begin
950 Result := not RecheckInnerOuter(e) or (IsOuter(e.OutRec) <> IsFront(e));
951 if Result then SwapSides(e.OutRec);
952end;
953//------------------------------------------------------------------------------
954
955procedure SetOwnerAndInnerOuterState(e: PActive);
956var
957 e2: PActive;
958 outRec: TOutRec;
959begin
960 outRec := e.OutRec;
961 if IsOpen(e) then
962 begin
963 outRec.Owner := nil;
964 outRec.State := osOpen;
965 Exit;
966 end;
967 // set owner ...
968 if IsHeadingLeftHorz(e) then
969 begin
970 e2 := e.NextInAEL; // ie assess state from opposite direction
971 while assigned(e2) and (not IsHotEdge(e2) or IsOpen(e2)) do
972 e2 := e2.NextInAEL;
973 if not assigned(e2) then outRec.Owner := nil
974 else if IsOuter(e2.OutRec) = (e2.OutRec.frontE = e2) then
975 outRec.Owner := e2.OutRec.Owner
976 else
977 outRec.Owner := e2.OutRec;
978 end else
979 begin
980 e2 := GetPrevHotEdge(e);
981 if not assigned(e2) then
982 outRec.Owner := nil
983 else if IsOuter(e2.OutRec) = (e2.OutRec.backE = e2) then
984 outRec.Owner := e2.OutRec.Owner
985 else
986 outRec.Owner := e2.OutRec;
987 end;
988
989 // set inner/outer ...
990 if not assigned(outRec.Owner) or IsInner(outRec.Owner) then
991 outRec.State := osOuter else
992 outRec.State := osInner;
993
994end;
995//------------------------------------------------------------------------------
996
997function EdgesAdjacentInAEL(node: PIntersectNode): Boolean;
998 {$IFDEF USEINLINING} inline; {$ENDIF}
999begin
1000 with node^ do
1001 Result := (Edge1.NextInAEL = Edge2) or (Edge1.PrevInAEL = Edge2);
1002end;
1003//------------------------------------------------------------------------------
1004
1005function IntersectListSort(node1, node2: Pointer): Integer;
1006begin
1007 Result := PIntersectNode(node2).Pt.Y - PIntersectNode(node1).Pt.Y;
1008 if (Result = 0) and (node1 <> node2) then
1009 Result := PIntersectNode(node1).Pt.X - PIntersectNode(node2).Pt.X;
1010end;
1011
1012//------------------------------------------------------------------------------
1013// TClipper methods ...
1014//------------------------------------------------------------------------------
1015
1016constructor TClipper.Create;
1017begin
1018 FLocMinList := TList.Create;
1019 FOutRecList := TList.Create;
1020 FIntersectList := TList.Create;
1021 FVertexList := TList.Create;
1022end;
1023//------------------------------------------------------------------------------
1024
1025destructor TClipper.Destroy;
1026begin
1027 Clear;
1028 FLocMinList.Free;
1029 FOutRecList.Free;
1030 FIntersectList.Free;
1031 FVertexList.Free;
1032 inherited;
1033end;
1034//------------------------------------------------------------------------------
1035
1036procedure TClipper.CleanUp;
1037var
1038 dummy: Int64;
1039begin
1040 try
1041 // in case of exceptions ...
1042 while assigned(FActives) do DeleteFromAEL(FActives);
1043 while assigned(FScanLine) do PopScanLine(dummy);
1044 DisposeIntersectNodes;
1045
1046 DisposeScanLineList;
1047 DisposeAllOutRecs;
1048 except
1049 end;
1050end;
1051//------------------------------------------------------------------------------
1052
1053procedure TClipper.Clear;
1054begin
1055 CleanUp;
1056 DisposeVerticesAndLocalMinima;
1057 FCurrentLocMinIdx := 0;
1058 FLocMinListSorted := false;
1059 FHasOpenPaths := False;
1060end;
1061//------------------------------------------------------------------------------
1062
1063procedure TClipper.Reset;
1064var
1065 i: Integer;
1066begin
1067 if not FLocMinListSorted then
1068 begin
1069 FLocMinList.Sort(LocMinListSort);
1070 FLocMinListSorted := true;
1071 end;
1072 for i := FLocMinList.Count -1 downto 0 do
1073 InsertScanLine(PLocalMinima(FLocMinList[i]).vertex.Pt.Y);
1074 FCurrentLocMinIdx := 0;
1075 FActives := nil;
1076 FSel := nil;
1077end;
1078//------------------------------------------------------------------------------
1079
1080procedure TClipper.InsertScanLine(const Y: Int64);
1081var
1082 newSl, sl: PScanLine;
1083begin
1084 // The scanline list is a single-linked list of all the Y coordinates of
1085 // subject and clip vertices in the clipping operation (sorted descending).
1086 // However, only scanline Y's at Local Minima are inserted before clipping
1087 // starts. While scanlines are removed sequentially during the sweep, new
1088 // scanlines are only inserted whenever edge bounds are updated. This keeps
1089 // the scanline list relatively short, optimising performance.
1090 if not Assigned(FScanLine) then
1091 begin
1092 new(newSl);
1093 newSl.Y := Y;
1094 FScanLine := newSl;
1095 newSl.Next := nil;
1096 end else if Y > FScanLine.Y then
1097 begin
1098 new(newSl);
1099 newSl.Y := Y;
1100 newSl.Next := FScanLine;
1101 FScanLine := newSl;
1102 end else
1103 begin
1104 sl := FScanLine;
1105 while Assigned(sl.Next) and (Y <= sl.Next.Y) do
1106 sl := sl.Next;
1107 if Y = sl.Y then Exit; // skip duplicates
1108 new(newSl);
1109 newSl.Y := Y;
1110 newSl.Next := sl.Next;
1111 sl.Next := newSl;
1112 end;
1113end;
1114//------------------------------------------------------------------------------
1115
1116function TClipper.PopScanLine(out Y: Int64): Boolean;
1117var
1118 sl: PScanLine;
1119begin
1120 Result := assigned(FScanLine);
1121 if not Result then Exit;
1122 Y := FScanLine.Y;
1123 sl := FScanLine;
1124 FScanLine := FScanLine.Next;
1125 dispose(sl);
1126end;
1127//------------------------------------------------------------------------------
1128
1129function TClipper.PopLocalMinima(Y: Int64;
1130 out localMinima: PLocalMinima): Boolean;
1131begin
1132 Result := false;
1133 if FCurrentLocMinIdx = FLocMinList.Count then Exit;
1134 localMinima := PLocalMinima(FLocMinList[FCurrentLocMinIdx]);
1135 if (localMinima.vertex.Pt.Y = Y) then
1136 begin
1137 inc(FCurrentLocMinIdx);
1138 Result := true;
1139 end;
1140end;
1141//------------------------------------------------------------------------------
1142
1143procedure TClipper.DisposeScanLineList;
1144var
1145 sl: PScanLine;
1146begin
1147 while Assigned(FScanLine) do
1148 begin
1149 sl := FScanLine.Next;
1150 Dispose(FScanLine);
1151 FScanLine := sl;
1152 end;
1153end;
1154//------------------------------------------------------------------------------
1155
1156procedure TClipper.DisposeOutRec(index: Integer);
1157var
1158 outRec: TOutRec;
1159begin
1160 outRec := FOutRecList[index];
1161 if Assigned(outRec.Pts) then DisposePolyPts(outRec.Pts);
1162 outRec.Free;
1163end;
1164//------------------------------------------------------------------------------
1165
1166procedure TClipper.DisposeAllOutRecs;
1167var
1168 i: Integer;
1169begin
1170 for i := 0 to FOutRecList.Count -1 do DisposeOutRec(i);
1171 FOutRecList.Clear;
1172end;
1173//------------------------------------------------------------------------------
1174
1175procedure TClipper.DisposeVerticesAndLocalMinima;
1176var
1177 i: Integer;
1178begin
1179 for i := 0 to FLocMinList.Count -1 do
1180 Dispose(PLocalMinima(FLocMinList[i]));
1181 FLocMinList.Clear;
1182 for i := 0 to FVertexList.Count -1 do FreeMem(FVertexList[i]);
1183 FVertexList.Clear;
1184end;
1185//------------------------------------------------------------------------------
1186
1187procedure TClipper.AddPathToVertexList(const path: TArrayOfFloatPoint;
1188 polyType: TPathType; isOpen: Boolean);
1189var
1190 i, j, pathLen: Integer;
1191 isFlat, goingUp, p0IsMinima, p0IsMaxima: Boolean;
1192 v: PVertex;
1193 va: PVertexArray;
1194
1195 procedure AddLocMin(vert: PVertex);
1196 var
1197 lm: PLocalMinima;
1198 begin
1199 if vfLocMin in vert.flags then Exit; // ensures vertex is added only once
1200 Include(vert.flags, vfLocMin);
1201 new(lm);
1202 lm.vertex := vert;
1203 lm.PolyType := polyType;
1204 lm.IsOpen := isOpen;
1205 FLocMinList.Add(lm); // nb: sorted in Reset()
1206 end;
1207 //----------------------------------------------------------------------------
1208
1209begin
1210 pathLen := length(path);
1211 if (pathLen < 2) then Exit;
1212
1213 p0IsMinima := false;
1214 p0IsMaxima := false;
1215 i := 1;
1216 // find the first non-horizontal segment in the path ...
1217 while (i < pathLen) and (path[i].Y = path[0].Y) do inc(i);
1218 isFlat := i = pathLen;
1219 if isFlat then
1220 begin
1221 if not isOpen then Exit; // Ignore closed paths that have ZERO area.
1222 goingUp := false; // And this just stops a compiler warning.
1223 end else
1224 begin
1225 goingUp := path[i].Y < path[0].Y;
1226 if goingUp then
1227 begin
1228 i := pathLen -1;
1229 while path[i].Y = path[0].Y do dec(i);
1230 p0IsMinima := path[i].Y < path[0].Y; // p[0].Y == a minima
1231 end else
1232 begin
1233 i := pathLen -1;
1234 while path[i].Y = path[0].Y do dec(i);
1235 p0IsMaxima := path[i].Y > path[0].Y; // p[0].Y == a maxima
1236 end;
1237 end;
1238
1239 GetMem(va, sizeof(TVertex) * pathLen);
1240 FVertexList.Add(va);
1241
1242 va[0].Pt := Point64(path[0]);
1243 va[0].flags := [];
1244 if isOpen then
1245 begin
1246 include(va[0].flags, vfOpenStart);
1247 if goingUp then
1248 AddLocMin(@va[0]) else
1249 include(va[0].flags, vfLocMax);
1250 end;
1251
1252 // nb: polygon orientation is determined later (see InsertLocalMinimaIntoAEL).
1253 i := 0;
1254 for j := 1 to pathLen -1 do
1255 begin
1256 va[j].Pt := Point64(path[j]);
1257 if PointsEqual(va[j].Pt, va[i].Pt) then Continue;
1258 va[j].flags := [];
1259 va[i].next := @va[j];
1260 va[j].prev := @va[i];
1261 if (path[j].Y > path[i].Y) and goingUp then
1262 begin
1263 include(va[i].flags, vfLocMax);
1264 goingUp := false;
1265 end
1266 else if (path[j].Y < path[i].Y) and not goingUp then
1267 begin
1268 goingUp := true;
1269 AddLocMin(@va[i]);
1270 end;
1271 i := j;
1272 end;
1273 // i: index of the last vertex in the path.
1274 va[i].next := @va[0];
1275 va[0].prev := @va[i];
1276
1277 if isOpen then
1278 begin
1279 include(va[i].flags, vfOpenEnd);
1280 if goingUp then
1281 include(va[i].flags, vfLocMax) else
1282 AddLocMin(@va[i]);
1283 end
1284 else if goingUp then
1285 begin
1286 // going up so find local maxima ...
1287 v := @va[i];
1288 while (v.Next.Pt.Y <= v.Pt.Y) do v := v.next;
1289 include(v.flags, vfLocMax);
1290 if p0IsMinima then AddLocMin(@va[0]); // ie just turned to going up
1291 end else
1292 begin
1293 // going down so find local minima ...
1294 v := @va[i];
1295 while (v.Next.Pt.Y >= v.Pt.Y) do v := v.next;
1296 AddLocMin(v);
1297 if p0IsMaxima then include(va[0].flags, vfLocMax);
1298 end;
1299end;
1300//------------------------------------------------------------------------------
1301
1302procedure TClipper.AddPath(const path64: TArrayOfFloatPoint;
1303 PolyType: TPathType; isOpen: Boolean);
1304begin
1305 if isOpen then
1306 begin
1307 if (PolyType = ptClip) then
1308 raise EClipperLibException.Create(rsClipper_OpenPathErr);
1309 FHasOpenPaths := true;
1310 end;
1311 FLocMinListSorted := false;
1312 AddPathToVertexList(path64, polyType, isOpen);
1313end;
1314//------------------------------------------------------------------------------
1315
1316procedure TClipper.AddPath(const path: TArrayOfFixedPoint;
1317 PolyType: TPathType; isOpen: Boolean);
1318begin
1319 AddPathToVertexList(FixedToFloat(path), polyType, isOpen);
1320end;
1321//------------------------------------------------------------------------------
1322
1323procedure TClipper.AddPaths(const paths64: TArrayOfArrayOfFloatPoint;
1324 polyType: TPathType; isOpen: Boolean);
1325var
1326 i: Integer;
1327begin
1328 for i := 0 to high(paths64) do AddPath(paths64[i], polyType, isOpen);
1329end;
1330//------------------------------------------------------------------------------
1331
1332procedure TClipper.AddPaths(const paths: TArrayOfArrayOfFixedPoint;
1333 polyType: TPathType = ptSubject; isOpen: Boolean = false);
1334var
1335 i: Integer;
1336begin
1337 for i := 0 to high(paths) do AddPath(paths[i], polyType, isOpen);
1338end;
1339//------------------------------------------------------------------------------
1340
1341function TClipper.IsContributingClosed(e: PActive): Boolean;
1342begin
1343 Result := false;
1344 case FFillRule of
1345 frNonZero: if abs(e.WindCnt) <> 1 then Exit;
1346 frPositive: if (e.WindCnt <> 1) then Exit;
1347 frNegative: if (e.WindCnt <> -1) then Exit;
1348 end;
1349
1350 case FClipType of
1351 ctIntersection:
1352 case FFillRule of
1353 frEvenOdd, frNonZero: Result := (e.WindCnt2 <> 0);
1354 frPositive: Result := (e.WindCnt2 > 0);
1355 frNegative: Result := (e.WindCnt2 < 0);
1356 end;
1357 ctUnion:
1358 case FFillRule of
1359 frEvenOdd, frNonZero: Result := (e.WindCnt2 = 0);
1360 frPositive: Result := (e.WindCnt2 <= 0);
1361 frNegative: Result := (e.WindCnt2 >= 0);
1362 end;
1363 ctDifference:
1364 if GetPolyType(e) = ptSubject then
1365 case FFillRule of
1366 frEvenOdd, frNonZero: Result := (e.WindCnt2 = 0);
1367 frPositive: Result := (e.WindCnt2 <= 0);
1368 frNegative: Result := (e.WindCnt2 >= 0);
1369 end
1370 else
1371 case FFillRule of
1372 frEvenOdd, frNonZero: Result := (e.WindCnt2 <> 0);
1373 frPositive: Result := (e.WindCnt2 > 0);
1374 frNegative: Result := (e.WindCnt2 < 0);
1375 end;
1376 ctXor:
1377 Result := true;
1378 end;
1379end;
1380//------------------------------------------------------------------------------
1381
1382function TClipper.IsContributingOpen(e: PActive): Boolean;
1383begin
1384 case FClipType of
1385 ctIntersection:
1386 Result := (e.WindCnt2 <> 0);
1387 ctXor:
1388 Result := (e.WindCnt <> 0) <> (e.WindCnt2 <> 0);
1389 ctDifference:
1390 Result := (e.WindCnt2 = 0);
1391 else // ctUnion:
1392 Result := (e.WindCnt = 0) and (e.WindCnt2 = 0);
1393 end;
1394end;
1395//------------------------------------------------------------------------------
1396
1397procedure TClipper.SetWindCountForClosedPathEdge(e: PActive);
1398var
1399 e2: PActive;
1400begin
1401 // Wind counts refer to polygon regions not edges, so here an edge's WindCnt
1402 // indicates the higher of the wind counts for the two regions touching the
1403 // edge. (nb: Adjacent regions can only ever have their wind counts differ by
1404 // one. Also, open paths have no meaningful wind directions or counts.)
1405
1406 e2 := e.PrevInAEL;
1407 // find the nearest closed edge of the same PolyType in AEL (heading left)
1408 while Assigned(e2) and (not IsSamePolyType(e2, e) or IsOpen(e2)) do
1409 e2 := e2.PrevInAEL;
1410
1411 if not Assigned(e2) then
1412 begin
1413 e.WindCnt := e.WindDx;
1414 e2 := FActives;
1415 end
1416 else if (FFillRule = frEvenOdd) then
1417 begin
1418 e.WindCnt := e.WindDx;
1419 e.WindCnt2 := e2.WindCnt2;
1420 e2 := e2.NextInAEL;
1421 end else
1422 begin
1423 // NonZero, positive, or negative filling here ...
1424 // if e's WindCnt is in the SAME direction as its WindDx, then polygon
1425 // filling will be on the right of 'e'.
1426 // nb: neither e2.WindCnt nor e2.WindDx should ever be 0.
1427 if (e2.WindCnt * e2.WindDx < 0) then
1428 begin
1429 // opposite directions so 'e' is outside 'e2' ...
1430 if (Abs(e2.WindCnt) > 1) then
1431 begin
1432 // outside prev poly but still inside another.
1433 if (e2.WindDx * e.WindDx < 0) then
1434 // reversing direction so use the same WC
1435 e.WindCnt := e2.WindCnt else
1436 // otherwise keep 'reducing' the WC by 1 (ie towards 0) ...
1437 e.WindCnt := e2.WindCnt + e.WindDx;
1438 end
1439 // now outside all polys of same polytype so set own WC ...
1440 else e.WindCnt := e.WindDx;
1441 end else
1442 begin
1443 // 'e' must be inside 'e2'
1444 if (e2.WindDx * e.WindDx < 0) then
1445 // reversing direction so use the same WC
1446 e.WindCnt := e2.WindCnt
1447 else
1448 // otherwise keep 'increasing' the WC by 1 (ie away from 0) ...
1449 e.WindCnt := e2.WindCnt + e.WindDx;
1450 end;
1451 e.WindCnt2 := e2.WindCnt2;
1452 e2 := e2.NextInAEL;
1453 end;
1454
1455 // update WindCnt2 ...
1456 if FFillRule = frEvenOdd then
1457 while (e2 <> e) do
1458 begin
1459 if IsSamePolyType(e2, e) or IsOpen(e2) then // do nothing
1460 else if e.WindCnt2 = 0 then e.WindCnt2 := 1
1461 else e.WindCnt2 := 0;
1462 e2 := e2.NextInAEL;
1463 end
1464 else
1465 while (e2 <> e) do
1466 begin
1467 if not IsSamePolyType(e2, e) and not IsOpen(e2) then
1468 Inc(e.WindCnt2, e2.WindDx);
1469 e2 := e2.NextInAEL;
1470 end;
1471end;
1472//------------------------------------------------------------------------------
1473
1474procedure TClipper.SetWindCountForOpenPathEdge(e: PActive);
1475var
1476 e2: PActive;
1477 cnt1, cnt2: Integer;
1478begin
1479 e2 := FActives;
1480 if FFillRule = frEvenOdd then
1481 begin
1482 cnt1 := 0;
1483 cnt2 := 0;
1484 while (e2 <> e) do
1485 begin
1486 if (GetPolyType(e2) = ptClip) then inc(cnt2)
1487 else if not IsOpen(e2) then inc(cnt1);
1488 e2 := e2.NextInAEL;
1489 end;
1490 if Odd(cnt1) then e.WindCnt := 1 else e.WindCnt := 0;
1491 if Odd(cnt2) then e.WindCnt2 := 1 else e.WindCnt2 := 0;
1492 end else
1493 begin
1494 // if FClipType in [ctUnion, ctDifference] then e.WindCnt := e.WindDx;
1495 while (e2 <> e) do
1496 begin
1497 if (GetPolyType(e2) = ptClip) then inc(e.WindCnt2, e2.WindDx)
1498 else if not IsOpen(e2) then inc(e.WindCnt, e2.WindDx);
1499 e2 := e2.NextInAEL;
1500 end;
1501 end;
1502end;
1503//------------------------------------------------------------------------------
1504
1505function IsValidAelOrder(a1, a2: PActive): Boolean;
1506var
1507 pt1, pt2: TPoint64;
1508 op1, op2: PVertex;
1509 X: Int64;
1510begin
1511 if a2.CurrX <> a1.CurrX then
1512 begin
1513 Result := a2.CurrX > a1.CurrX;
1514 Exit;
1515 end;
1516
1517 pt1 := a1.Bot; pt2 := a2.Bot;
1518 op1 := a1.VertTop; op2 := a2.VertTop;
1519 while true do
1520 begin
1521 if op1.Pt.Y >= op2.Pt.Y then
1522 begin
1523 X := TopX(pt2, op2.Pt, op1.Pt.Y) - op1.Pt.X;
1524 Result := X > 0;
1525 if X <> 0 then Exit;
1526 if op2.Pt.Y = op1.Pt.Y then
1527 begin
1528 pt2 := op2.Pt;
1529 op2 := NextVertex(op2, IsLeftBound(a2));
1530 end;
1531 pt1 := op1.Pt;
1532 op1 := NextVertex(op1, IsLeftBound(a1));
1533 end else
1534 begin
1535 X := op2.Pt.X - TopX(pt1, op1.Pt, op2.Pt.Y);
1536 Result := X > 0;
1537 if X <> 0 then Exit;
1538 pt2 := op2.Pt;
1539 op2 := NextVertex(op2, IsLeftBound(a2));
1540 end;
1541
1542 if (op1.Pt.Y > pt1.Y) then
1543 begin
1544 Result := (a1.WindDx > 0) <> IsClockwise(PrevVertex(op1, a1.WindDx > 0));
1545 Exit;
1546 end else if (op2.Pt.Y > pt2.Y) then
1547 begin
1548 Result := (a2.WindDx > 0) = IsClockwise(PrevVertex(op2, a2.WindDx > 0));
1549 Exit;
1550 end;
1551 end;
1552 Result := true;
1553end;
1554//------------------------------------------------------------------------------
1555
1556procedure TClipper.InsertLeftEdge(e: PActive);
1557var
1558 e2: PActive;
1559begin
1560 if not Assigned(FActives) then
1561 begin
1562 e.PrevInAEL := nil;
1563 e.NextInAEL := nil;
1564 FActives := e;
1565 end
1566 else if IsValidAelOrder(e, FActives) then
1567 begin
1568 e.PrevInAEL := nil;
1569 e.NextInAEL := FActives;
1570 FActives.PrevInAEL := e;
1571 FActives := e;
1572 end else
1573 begin
1574 e2 := FActives;
1575 while Assigned(e2.NextInAEL) and IsValidAelOrder(e2.NextInAEL, e) do
1576 e2 := e2.NextInAEL;
1577 e.NextInAEL := e2.NextInAEL;
1578 if Assigned(e2.NextInAEL) then e2.NextInAEL.PrevInAEL := e;
1579 e.PrevInAEL := e2;
1580 e2.NextInAEL := e;
1581 end;
1582end;
1583//----------------------------------------------------------------------
1584
1585procedure InsertRightEdge(e, e2: PActive);
1586begin
1587 e2.NextInAEL := e.NextInAEL;
1588 if Assigned(e.NextInAEL) then e.NextInAEL.PrevInAEL := e2;
1589 e2.PrevInAEL := e;
1590 e.NextInAEL := e2;
1591end;
1592//----------------------------------------------------------------------
1593
1594procedure TClipper.InsertLocalMinimaIntoAEL(const botY: Int64);
1595var
1596 leftB, rightB: PActive;
1597 locMin: PLocalMinima;
1598 contributing: Boolean;
1599begin
1600 // Add local minima (if any) at BotY ...
1601 // nb: horizontal local minima edges should contain locMin.vertex.prev
1602
1603 while PopLocalMinima(botY, locMin) do
1604 begin
1605 if (vfOpenStart in locMin.vertex.flags) then
1606 begin
1607 leftB := nil;
1608 end else
1609 begin
1610 new(leftB);
1611 FillChar(leftB^, sizeof(TActive), 0);
1612 leftB.LocMin := locMin;
1613 leftB.OutRec := nil;
1614 leftB.Bot := locMin.vertex.Pt;
1615 leftB.vertTop := locMin.vertex.prev; // ie descending
1616 leftB.Top := leftB.vertTop.Pt;
1617 leftB.CurrX := leftB.Bot.X;
1618 leftB.WindDx := -1;
1619 SetDx(leftB);
1620 end;
1621
1622 if (vfOpenEnd in locMin.vertex.flags) then
1623 begin
1624 rightB := nil;
1625 end else
1626 begin
1627 new(rightB);
1628 FillChar(rightB^, sizeof(TActive), 0);
1629 rightB.LocMin := locMin;
1630 rightB.OutRec := nil;
1631 rightB.Bot := locMin.vertex.Pt;
1632 rightB.vertTop := locMin.vertex.next; // ie ascending
1633 rightB.Top := rightB.vertTop.Pt;
1634 rightB.CurrX := rightB.Bot.X;
1635 rightB.WindDx := 1;
1636 SetDx(rightB);
1637 end;
1638 // Currently LeftB is just the descending bound and RightB is the ascending.
1639 // Now if the LeftB isn't on the left of RightB then we need swap them.
1640 if assigned(leftB) and assigned(rightB) then
1641 begin
1642 if IsHorizontal(leftB) then
1643 begin
1644 if IsHeadingRightHorz(leftB) then SwapActives(leftB, rightB);
1645 end
1646 else if IsHorizontal(rightB) then
1647 begin
1648 if IsHeadingLeftHorz(rightB) then SwapActives(leftB, rightB);
1649 end
1650 else if (leftB.Dx < rightB.Dx) then SwapActives(leftB, rightB);
1651 end
1652 else if not assigned(leftB) then
1653 begin
1654 leftB := rightB;
1655 rightB := nil;
1656 end;
1657
1658 InsertLeftEdge(leftB); // /// //
1659 // todo: further validation of position in AEL ???
1660
1661 if IsOpen(leftB) then
1662 begin
1663 SetWindCountForOpenPathEdge(leftB);
1664 contributing := IsContributingOpen(leftB);
1665 end else
1666 begin
1667 SetWindCountForClosedPathEdge(leftB);
1668 contributing := IsContributingClosed(leftB);
1669 end;
1670
1671 if assigned(rightB) then
1672 begin
1673 rightB.WindCnt := leftB.WindCnt;
1674 rightB.WindCnt2 := leftB.WindCnt2;
1675 InsertRightEdge(leftB, rightB); // /// //
1676 if contributing then
1677 AddLocalMinPoly(leftB, rightB, leftB.Bot, true);
1678
1679 if IsHorizontal(rightB) then
1680 PushHorz(rightB) else
1681 InsertScanLine(rightB.Top.Y);
1682 end
1683 else if contributing then
1684 StartOpenPath(leftB, leftB.Bot);
1685
1686 if IsHorizontal(leftB) then
1687 PushHorz(leftB) else
1688 InsertScanLine(leftB.Top.Y);
1689 end;
1690end;
1691//------------------------------------------------------------------------------
1692
1693procedure TClipper.PushHorz(e: PActive);
1694begin
1695 if assigned(FSel) then
1696 e.NextInSEL := FSel else
1697 e.NextInSEL := nil;
1698 FSel := e;
1699end;
1700//------------------------------------------------------------------------------
1701
1702function TClipper.PopHorz(out e: PActive): Boolean;
1703begin
1704 Result := assigned(FSel);
1705 if not Result then Exit;
1706 e := FSel;
1707 FSel := FSel.NextInSEL;
1708end;
1709//------------------------------------------------------------------------------
1710
1711procedure TClipper.AddLocalMinPoly(e1, e2: PActive; const pt: TPoint64;
1712 IsNew: Boolean = false; orientationCheckRequired: Boolean = false);
1713var
1714 outRec: TOutRec;
1715 op: TOutPt;
1716begin
1717 outRec := TOutRec.Create;
1718 outRec.Idx := FOutRecList.Add(outRec);
1719 outRec.Pts := nil;
1720
1721 e1.OutRec := outRec;
1722 SetOwnerAndInnerOuterState(e1);
1723 // flag when orientatation needs to be rechecked later ...
1724 if orientationCheckRequired then SetCheckFlag(outRec);
1725
1726 e2.OutRec := outRec;
1727 if not IsOpen(e1) then
1728 begin
1729 // Setting the owner and inner/outer states (above) is an essential
1730 // precursor to setting edge 'sides' (ie left and right sides of output
1731 // polygons) and hence the orientation of output paths ...
1732 if IsOuter(outRec) = IsNew then
1733 SetSides(outRec, e1, e2) else
1734 SetSides(outRec, e2, e1);
1735 end;
1736
1737 op := TOutPt.Create;
1738 outRec.Pts := op;
1739 op.Pt := pt;
1740 op.Prev := op;
1741 op.Next := op;
1742
1743 // nb: currently e1.NextInAEL == e2 but this could change on return
1744end;
1745//------------------------------------------------------------------------------
1746
1747procedure TClipper.AddLocalMaxPoly(e1, e2: PActive; const pt: TPoint64);
1748var
1749 op: TOutPt;
1750begin
1751 if not IsOpen(e1) and (IsFront(e1) = IsFront(e2)) then
1752 if not FixSides(e1) then FixSides(e2);
1753
1754 op := AddOutPt(e1, pt);
1755 // AddOutPt(e2, pt); // this may no longer be necessary
1756
1757 if (e1.OutRec = e2.OutRec) then
1758 begin
1759 if e1.OutRec.State in [osOuterCheck, osInnerCheck] then
1760 RecheckInnerOuter(e1);
1761
1762 // nb: IsClockwise() is generally faster than Area() but will occasionally
1763 // give false positives when there are tiny self-intersections at the top...
1764 if IsOuter(e1.OutRec) then
1765 begin
1766 if not IsClockwise(op) and (Area(op) < 0) then
1767 ReverseOutPts(e1.OutRec.Pts);
1768 end else
1769 begin
1770 if IsClockwise(op) and (Area(op) > 0) then
1771 ReverseOutPts(e1.OutRec.Pts);
1772 end;
1773
1774 e1.outRec.frontE := nil;
1775 e1.outRec.backE := nil;
1776 e1.OutRec := nil;
1777 e2.OutRec := nil;
1778 end
1779 // and to preserve the winding orientation of Outrec ...
1780 else if e1.OutRec.Idx < e2.OutRec.Idx then
1781 JoinOutrecPaths(e1, e2) else
1782 JoinOutrecPaths(e2, e1);
1783
1784end;
1785//------------------------------------------------------------------------------
1786
1787procedure TClipper.JoinOutrecPaths(e1, e2: PActive);
1788var
1789 p1_start, p1_end, p2_start, p2_end: TOutPt;
1790begin
1791 if (IsFront(e1) = IsFront(e2)) then
1792 begin
1793 // one or other 'side' must be wrong ...
1794 if IsOpen(e1) then SwapSides(e2.OutRec)
1795 else if not FixSides(e1) and not FixSides(e2) then
1796 raise EClipperLibException.Create(rsClipper_ClippingErr);
1797 if e1.OutRec.Owner = e2.OutRec then
1798 e1.OutRec.Owner := e2.OutRec.Owner;
1799 end;
1800
1801 // join e2 outrec path onto e1 outrec path and then delete e2 outrec path
1802 // pointers. (see joining_outpt.svg)
1803 p1_start := e1.OutRec.Pts;
1804 p2_start := e2.OutRec.Pts;
1805 p1_end := p1_start.Next;
1806 p2_end := p2_start.Next;
1807
1808 if IsFront(e1) then
1809 begin
1810 p2_end.Prev := p1_start;
1811 p1_start.Next := p2_end;
1812 p2_start.Next := p1_end;
1813 p1_end.Prev := p2_start;
1814 e1.OutRec.Pts := p2_start;
1815 e1.OutRec.frontE := e2.OutRec.frontE;
1816 if not IsOpen(e1) then e1.OutRec.frontE.OutRec := e1.OutRec;
1817 // strip duplicates ...
1818 if (p2_end <> p2_start) and PointsEqual(p2_end.Pt, p2_end.Prev.Pt) then
1819 DisposeOutPt(p2_end);
1820 end else
1821 begin
1822 p1_end.Prev := p2_start;
1823 p2_start.Next := p1_end;
1824 p1_start.Next := p2_end;
1825 p2_end.Prev := p1_start;
1826 e1.OutRec.backE := e2.OutRec.backE;
1827 if not IsOpen(e1) then e1.OutRec.backE.OutRec := e1.OutRec;
1828 // strip duplicates ...
1829 if (p1_end <> p1_start) and PointsEqual(p1_end.Pt, p1_end.Prev.Pt) then
1830 DisposeOutPt(p1_end);
1831 end;
1832
1833 if PointsEqual(e1.OutRec.Pts.Pt, e1.OutRec.Pts.Prev.Pt) and
1834 not IsInvalidPath(e1.OutRec.Pts) then
1835 DisposeOutPt(e1.OutRec.Pts.Prev);
1836
1837 // after joining, the e2.OutRec must contains no vertices ...
1838 e2.OutRec.frontE := nil;
1839 e2.OutRec.backE := nil;
1840 e2.OutRec.Pts := nil;
1841 e2.OutRec.Owner := e1.OutRec; // this may be redundant
1842
1843 // and e1 and e2 are maxima and are about to be dropped from the Actives list.
1844 e1.OutRec := nil;
1845 e2.OutRec := nil;
1846end;
1847//------------------------------------------------------------------------------
1848
1849function TClipper.AddOutPt(e: PActive; const pt: TPoint64): TOutPt;
1850var
1851 opFront, opBack: TOutPt;
1852 toFront: Boolean;
1853 outrec: TOutRec;
1854begin
1855 // Outrec.OutPts: a circular doubly-linked-list of POutPt where ...
1856 // opFront[.Prev]* ~~~> opBack & opBack == opFront.Next
1857 outrec := e.OutRec;
1858 toFront := IsFront(e);
1859 opFront := outrec.Pts;
1860 opBack := opFront.Next;
1861 if toFront and PointsEqual(pt, opFront.Pt) then
1862 Result := opFront
1863 else if not toFront and PointsEqual(pt, opBack.Pt) then
1864 Result := opBack
1865 else
1866 begin
1867 Result := TOutPt.Create;
1868 Result.Pt := pt;
1869 opBack.Prev := Result;
1870 Result.Prev := opFront;
1871 Result.Next := opBack;
1872 opFront.Next := Result;
1873 if toFront then outrec.Pts := Result;
1874 end;
1875end;
1876//------------------------------------------------------------------------------
1877
1878procedure TClipper.StartOpenPath(e: PActive; const pt: TPoint64);
1879var
1880 outRec: TOutRec;
1881 op: TOutPt;
1882begin
1883 outRec := TOutRec.Create;
1884 outRec.Idx := FOutRecList.Add(outRec);
1885 outRec.Owner := nil;
1886 outRec.State := osOpen;
1887 outRec.Pts := nil;
1888 outRec.frontE := nil;
1889 outRec.backE := nil;
1890 e.OutRec := outRec;
1891
1892 op := TOutPt.Create;
1893 outRec.Pts := op;
1894 op.Pt := pt;
1895 op.Prev := op;
1896 op.Next := op;
1897end;
1898//------------------------------------------------------------------------------
1899
1900procedure TClipper.UpdateEdgeIntoAEL(var e: PActive);
1901begin
1902 e.Bot := e.Top;
1903 e.vertTop := NextVertex(e);
1904 e.Top := e.vertTop.Pt;
1905 e.CurrX := e.Bot.X;
1906 SetDx(e);
1907 if not IsHorizontal(e) then InsertScanLine(e.Top.Y);
1908end;
1909//------------------------------------------------------------------------------
1910
1911procedure TClipper.IntersectEdges(e1, e2: PActive;
1912 const pt: TPoint64; orientationCheckRequired: Boolean = false);
1913var
1914 e1WindCnt, e2WindCnt, e1WindCnt2, e2WindCnt2: Integer;
1915begin
1916
1917 // MANAGE OPEN PATH INTERSECTIONS SEPARATELY ...
1918 if FHasOpenPaths and (IsOpen(e1) or IsOpen(e2)) then
1919 begin
1920 if (IsOpen(e1) and IsOpen(e2) ) then Exit;
1921 // the following line avoids duplicating a whole lot of code ...
1922 if IsOpen(e2) then SwapActives(e1, e2);
1923 case FClipType of
1924 ctIntersection, ctDifference:
1925 if IsSamePolyType(e1, e2) or (abs(e2.WindCnt) <> 1) then Exit;
1926 ctUnion:
1927 if IsHotEdge(e1) <> ((abs(e2.WindCnt) <> 1) or
1928 (IsHotEdge(e1) <> (e2.WindCnt2 <> 0))) then Exit; // just works!
1929 ctXor:
1930 if (abs(e2.WindCnt) <> 1) then Exit;
1931 end;
1932 // toggle contribution ...
1933 if IsHotEdge(e1) then
1934 begin
1935 AddOutPt(e1, pt);
1936 TerminateHotOpen(e1);
1937 end
1938 else StartOpenPath(e1, pt);
1939 Exit;
1940 end;
1941
1942 // UPDATE WINDING COUNTS...
1943
1944 if IsSamePolyType(e1, e2) then
1945 begin
1946 if FFillRule = frEvenOdd then
1947 begin
1948 e1WindCnt := e1.WindCnt;
1949 e1.WindCnt := e2.WindCnt;
1950 e2.WindCnt := e1WindCnt;
1951 end else
1952 begin
1953 if e1.WindCnt + e2.WindDx = 0 then
1954 e1.WindCnt := -e1.WindCnt else
1955 Inc(e1.WindCnt, e2.WindDx);
1956 if e2.WindCnt - e1.WindDx = 0 then
1957 e2.WindCnt := -e2.WindCnt else
1958 Dec(e2.WindCnt, e1.WindDx);
1959 end;
1960 end else
1961 begin
1962 if FFillRule <> frEvenOdd then Inc(e1.WindCnt2, e2.WindDx)
1963 else if e1.WindCnt2 = 0 then e1.WindCnt2 := 1
1964 else e1.WindCnt2 := 0;
1965
1966 if FFillRule <> frEvenOdd then Dec(e2.WindCnt2, e1.WindDx)
1967 else if e2.WindCnt2 = 0 then e2.WindCnt2 := 1
1968 else e2.WindCnt2 := 0;
1969 end;
1970
1971 case FFillRule of
1972 frPositive:
1973 begin
1974 e1WindCnt := e1.WindCnt;
1975 e2WindCnt := e2.WindCnt;
1976 end;
1977 frNegative:
1978 begin
1979 e1WindCnt := -e1.WindCnt;
1980 e2WindCnt := -e2.WindCnt;
1981 end;
1982 else
1983 begin
1984 e1WindCnt := abs(e1.WindCnt);
1985 e2WindCnt := abs(e2.WindCnt);
1986 end;
1987 end;
1988
1989 if (not IsHotEdge(e1) and not (e1WindCnt in [0,1])) or
1990 (not IsHotEdge(e2) and not (e2WindCnt in [0,1])) then Exit;
1991
1992 // NOW PROCESS THE INTERSECTION ...
1993
1994 // if both edges are 'hot' ...
1995 if IsHotEdge(e1) and IsHotEdge(e2) then
1996 begin
1997 if not (e1WindCnt in [0,1]) or not (e2WindCnt in [0,1]) or
1998 (not IsSamePolyType(e1, e2) and (fClipType <> ctXor)) then
1999 begin
2000 AddLocalMaxPoly(e1, e2, pt);
2001 end else if IsFront(e1) or (e1.OutRec = e2.OutRec) then
2002 begin
2003 AddLocalMaxPoly(e1, e2, pt);
2004 AddLocalMinPoly(e1, e2, pt);
2005 end else
2006 begin
2007 // right & left bounds touching, NOT maxima & minima ...
2008 AddOutPt(e1, pt);
2009 AddOutPt(e2, pt);
2010 SwapOutRecs(e1, e2);
2011 end;
2012 end
2013
2014 // if one or other edge is 'hot' ...
2015 else if IsHotEdge(e1) then
2016 begin
2017 AddOutPt(e1, pt);
2018 SwapOutRecs(e1, e2);
2019 end
2020 else if IsHotEdge(e2) then
2021 begin
2022 AddOutPt(e2, pt);
2023 SwapOutRecs(e1, e2);
2024 end
2025
2026 else // neither edge is 'hot'
2027 begin
2028 case FFillRule of
2029 frPositive:
2030 begin
2031 e1WindCnt2 := e1.WindCnt2;
2032 e2WindCnt2 := e2.WindCnt2;
2033 end;
2034 frNegative:
2035 begin
2036 e1WindCnt2 := -e1.WindCnt2;
2037 e2WindCnt2 := -e2.WindCnt2;
2038 end
2039 else
2040 begin
2041 e1WindCnt2 := abs(e1.WindCnt2);
2042 e2WindCnt2 := abs(e2.WindCnt2);
2043 end;
2044 end;
2045
2046 if not IsSamePolyType(e1, e2) then
2047 begin
2048 AddLocalMinPoly(e1, e2, pt, false, orientationCheckRequired);
2049 end
2050 else if (e1WindCnt = 1) and (e2WindCnt = 1) then
2051 case FClipType of
2052 ctIntersection:
2053 if (e1WindCnt2 > 0) and (e2WindCnt2 > 0) then
2054 AddLocalMinPoly(e1, e2, pt, false, orientationCheckRequired);
2055 ctUnion:
2056 if (e1WindCnt2 <= 0) and (e2WindCnt2 <= 0) then
2057 AddLocalMinPoly(e1, e2, pt, false, orientationCheckRequired);
2058 ctDifference:
2059 if ((GetPolyType(e1) = ptClip) and
2060 (e1WindCnt2 > 0) and (e2WindCnt2 > 0)) or
2061 ((GetPolyType(e1) = ptSubject) and
2062 (e1WindCnt2 <= 0) and (e2WindCnt2 <= 0)) then
2063 AddLocalMinPoly(e1, e2, pt, false, orientationCheckRequired);
2064 ctXor:
2065 AddLocalMinPoly(e1, e2, pt, false, orientationCheckRequired);
2066 end
2067 end;
2068end;
2069//------------------------------------------------------------------------------
2070
2071procedure TClipper.DeleteFromAEL(e: PActive);
2072var
2073 aelPrev, aelNext: PActive;
2074begin
2075 aelPrev := e.PrevInAEL;
2076 aelNext := e.NextInAEL;
2077 if not Assigned(aelPrev) and not Assigned(aelNext) and
2078 (e <> FActives) then Exit; // already deleted
2079 if Assigned(aelPrev) then aelPrev.NextInAEL := aelNext
2080 else FActives := aelNext;
2081 if Assigned(aelNext) then aelNext.PrevInAEL := aelPrev;
2082 Dispose(e);
2083end;
2084//------------------------------------------------------------------------------
2085
2086procedure TClipper.AdjustCurrXAndCopyToSEL(topY: Int64);
2087var
2088 e: PActive;
2089begin
2090 FSel := FActives;
2091 e := FActives;
2092 while Assigned(e) do
2093 begin
2094 e.PrevInSEL := e.PrevInAEL;
2095 e.NextInSEL := e.NextInAEL;
2096 e.CurrX := TopX(e, topY);
2097 e := e.NextInAEL;
2098 end;
2099end;
2100//------------------------------------------------------------------------------
2101
2102procedure TClipper.ExecuteInternal(clipType: TClipType;
2103 fillRule: TFillRule);
2104var
2105 Y: Int64;
2106 e: PActive;
2107begin
2108 if clipType = ctNone then Exit;
2109 FFillRule := fillRule;
2110 FClipType := clipType;
2111 Reset;
2112 if not PopScanLine(Y) then Exit;
2113 while true do
2114 begin
2115 InsertLocalMinimaIntoAEL(Y);
2116 while PopHorz(e) do DoHorizontal(e);
2117 FBotY := Y; // FBotY == bottom of scanbeam
2118 if not PopScanLine(Y) then Break; // Y new top of scanbeam
2119 DoIntersections(Y);
2120 DoTopOfScanbeam(Y);
2121 end;
2122end;
2123//------------------------------------------------------------------------------
2124
2125function TClipper.Execute(clipType: TClipType;
2126 fillRule: TFillRule; out closedPaths: TArrayOfArrayOfFloatPoint): Boolean;
2127var
2128 dummy: TArrayOfArrayOfFloatPoint;
2129begin
2130 Result := true;
2131 closedPaths := nil;
2132 try try
2133 ExecuteInternal(clipType, fillRule);
2134 BuildResult(closedPaths, dummy);
2135 except
2136 Result := false;
2137 end;
2138 finally
2139 CleanUp;
2140 end;
2141end;
2142//------------------------------------------------------------------------------
2143
2144function TClipper.Execute(clipType: TClipType;
2145 fillRule: TFillRule; out closedPaths: TArrayOfArrayOfFixedPoint): Boolean;
2146var
2147 tmp: TArrayOfArrayOfFloatPoint;
2148begin
2149 Result := Execute(clipType, fillRule, tmp);
2150 closedPaths := FloatToFixed(tmp);
2151end;
2152//------------------------------------------------------------------------------
2153
2154function TClipper.Execute(clipType: TClipType; fillRule: TFillRule;
2155 out closedPaths, openPaths: TArrayOfArrayOfFloatPoint): Boolean;
2156begin
2157 Result := true;
2158 closedPaths := nil;
2159 openPaths := nil;
2160 try try
2161 ExecuteInternal(clipType, fillRule);
2162 BuildResult(closedPaths, openPaths);
2163 except
2164 Result := false;
2165 end;
2166 finally
2167 CleanUp;
2168 end;
2169end;
2170//------------------------------------------------------------------------------
2171
2172function TClipper.Execute(clipType: TClipType; fillRule: TFillRule;
2173 out closedPaths, openPaths: TArrayOfArrayOfFixedPoint): Boolean;
2174var
2175 tmp, tmp2: TArrayOfArrayOfFloatPoint;
2176begin
2177 Result := Execute(clipType, fillRule, tmp, tmp2);
2178 closedPaths := FloatToFixed(tmp);
2179 openPaths := FloatToFixed(tmp2);
2180end;
2181//------------------------------------------------------------------------------
2182
2183procedure TClipper.DoIntersections(const topY: Int64);
2184begin
2185 if BuildIntersectList(topY) then
2186 try
2187 ProcessIntersectList;
2188 finally
2189 DisposeIntersectNodes;
2190 end;
2191end;
2192//------------------------------------------------------------------------------
2193
2194procedure TClipper.DisposeIntersectNodes;
2195var
2196 i: Integer;
2197begin
2198 for i := 0 to FIntersectList.Count - 1 do
2199 Dispose(IntersectNode[i]);
2200 FIntersectList.Clear;
2201end;
2202//------------------------------------------------------------------------------
2203
2204procedure TClipper.AddNewIntersectNode(e1, e2: PActive; topY: Int64);
2205var
2206 pt: TPoint64;
2207 node: PIntersectNode;
2208begin
2209 pt := GetIntersectPoint(e1, e2);
2210 // Rounding errors can occasionally place the calculated intersection
2211 // point either below or above the scanbeam, so check and correct ...
2212 if (pt.Y > FBotY) then
2213 begin
2214 // E.Curr.Y is still at the bottom of scanbeam here
2215 pt.Y := FBotY;
2216 // use the more vertical of the 2 edges to derive pt.X ...
2217 if (abs(e1.Dx) < abs(e2.Dx)) then
2218 pt.X := TopX(e1, FBotY) else
2219 pt.X := TopX(e2, FBotY);
2220 end
2221 else if pt.Y < topY then
2222 begin
2223 // TopY = top of scanbeam
2224 pt.Y := topY;
2225 if e1.Top.Y = topY then
2226 pt.X := e1.Top.X
2227 else if e2.Top.Y = topY then
2228 pt.X := e2.Top.X
2229 else if (abs(e1.Dx) < abs(e2.Dx)) then
2230 pt.X := e1.CurrX
2231 else
2232 pt.X := e2.CurrX;
2233 end;
2234
2235 new(node);
2236 node.Edge1 := e1;
2237 node.Edge2 := e2;
2238 node.Pt := pt;
2239 FIntersectList.Add(node);
2240end;
2241//------------------------------------------------------------------------------
2242
2243function TClipper.BuildIntersectList(const topY: Int64): Boolean;
2244var
2245 i, lCnt, rCnt, jumpSize: Integer;
2246 first, second, base, prevBase, p, n, tmp: PActive;
2247begin
2248 Result := false;
2249 if not Assigned(FActives) or not Assigned(FActives.NextInAEL) then Exit;
2250
2251 // Calculate edge positions at the top of the current scanbeam, and from this
2252 // we will determine the intersections required to reach these new positions.
2253 AdjustCurrXAndCopyToSEL(topY);
2254 // Track every edge intersection between the bottom and top of each scanbeam,
2255 // using a stable merge sort to ensure edges are adjacent when intersecting.
2256 // Re merge sorts see https://stackoverflow.com/a/46319131/359538
2257 jumpSize := 1;
2258 while (true) do
2259 begin
2260 first := FSel;
2261 prevBase := nil;
2262 // sort successive larger jump counts of nodes ...
2263 while assigned(first) do
2264 begin
2265 if (jumpSize = 1) then
2266 begin
2267 second := first.NextInSEL;
2268 if not assigned(second) then
2269 begin
2270 first.Jump := nil;
2271 break;
2272 end;
2273 first.Jump := second.NextInSEL;
2274 end else
2275 begin
2276 second := first.Jump;
2277 if not assigned(second) then
2278 begin
2279 first.Jump := nil;
2280 break;
2281 end;
2282 first.Jump := second.Jump;
2283 end;
2284
2285 // now sort first and second groups ...
2286 base := first;
2287 lCnt := jumpSize; rCnt := jumpSize;
2288 while (lCnt > 0) and (rCnt > 0) do
2289 begin
2290 if (first.CurrX > second.CurrX) then
2291 begin
2292 tmp := second.PrevInSEL;
2293
2294 // create intersect 'node' events for each time 'second' needs to
2295 // move left, ie intersecting with its prior edge ...
2296 for i := 1 to lCnt do
2297 begin
2298 AddNewIntersectNode(tmp, second, topY);
2299 tmp := tmp.PrevInSEL;
2300 end;
2301
2302 // now move the out of place 'second' to it's new position in SEL ...
2303 if (first = base) then
2304 begin
2305 if assigned(prevBase) then prevBase.Jump := second;
2306 base := second;
2307 base.Jump := first.Jump;
2308 if (first.PrevInSEL = nil) then FSel := second;
2309 end;
2310 tmp := second.NextInSEL;
2311
2312 // first remove 'second' from list ...
2313 p := second.PrevInSEL;
2314 n := second.NextInSEL;
2315 p.NextInSEL := n;
2316 if Assigned(n) then n.PrevInSEL := p;
2317 // and then reinsert 'second' into list just before 'first' ...
2318 p := first.PrevInSEL;
2319 if assigned(p) then p.NextInSEL := second;
2320 first.PrevInSEL := second;
2321 second.PrevInSEL := p;
2322 second.NextInSEL := first;
2323
2324 second := tmp;
2325 if not assigned(second) then break;
2326 dec(rCnt);
2327 end else
2328 begin
2329 first := first.NextInSEL;
2330 dec(lCnt);
2331 end;
2332 end;
2333 first := base.Jump;
2334 prevBase := base;
2335 end;
2336 if FSel.Jump = nil then Break
2337 else jumpSize := jumpSize shl 1;
2338 end;
2339 Result := FIntersectList.Count > 0;
2340end;
2341//------------------------------------------------------------------------------
2342
2343function TClipper.GetIntersectNode(index: Integer): PIntersectNode;
2344begin
2345 Result := PIntersectNode(FIntersectList[index]);
2346end;
2347//------------------------------------------------------------------------------
2348
2349procedure TClipper.ProcessIntersectList;
2350var
2351 i, j, highI: Integer;
2352 node: PIntersectNode;
2353begin
2354 // We now have a list of intersections required so that edges will be
2355 // correctly positioned at the top of the scanbeam. However, it's important
2356 // that edge intersections are processed from the bottom up, but it's also
2357 // crucial that intersections only occur between adjacent edges.
2358
2359 // First we do a quicksort so intersections proceed in a bottom up order ...
2360 FIntersectList.Sort(IntersectListSort);
2361
2362 // Now as we process these intersections, we must sometimes adjust the order
2363 // to ensure that intersecting edges are always adjacent ...
2364 highI := FIntersectList.Count - 1;
2365 for i := 0 to highI do
2366 begin
2367 if not EdgesAdjacentInAEL(FIntersectList[i]) then
2368 begin
2369 j := i + 1;
2370 while not EdgesAdjacentInAEL(FIntersectList[j]) do inc(j);
2371 // Swap IntersectNodes ...
2372 node := FIntersectList[i];
2373 FIntersectList[i] := FIntersectList[j];
2374 FIntersectList[j] := node;
2375 end;
2376
2377 with IntersectNode[i]^ do
2378 begin
2379 // Occasionally a non-minima intersection is processed before its own
2380 // minima. This causes problems with orientation so we need to flag it ...
2381 if (i < highI) and (IntersectNode[i+1].Pt.Y > Pt.Y) then
2382 IntersectEdges(Edge1, Edge2, Pt, true) else
2383 IntersectEdges(Edge1, Edge2, Pt);
2384 SwapPositionsInAEL(Edge1, Edge2);
2385 end;
2386 end;
2387end;
2388//------------------------------------------------------------------------------
2389
2390procedure TClipper.SwapPositionsInAEL(e1, e2: PActive);
2391var
2392 prev, next: PActive;
2393begin
2394 // preconditon: e1 must be immediately to the left of e2
2395 next := e2.NextInAEL;
2396 if Assigned(next) then next.PrevInAEL := e1;
2397 prev := e1.PrevInAEL;
2398 if Assigned(prev) then prev.NextInAEL := e2;
2399 e2.PrevInAEL := prev;
2400 e2.NextInAEL := e1;
2401 e1.PrevInAEL := e2;
2402 e1.NextInAEL := next;
2403 if not Assigned(e2.PrevInAEL) then FActives := e2;
2404end;
2405//------------------------------------------------------------------------------
2406
2407procedure TClipper.DoHorizontal(horzEdge: PActive);
2408var
2409 e, maxPair: PActive;
2410 horzLeft, horzRight: Int64;
2411 isLeftToRight: Boolean;
2412 pt: TPoint64;
2413 isMax: Boolean;
2414
2415 procedure ResetHorzDirection;
2416 var
2417 e: PActive;
2418 begin
2419 if (horzEdge.Bot.X = horzEdge.Top.X) then
2420 begin
2421 // the horizontal edge is going nowhere ...
2422 horzLeft := horzEdge.CurrX;
2423 horzRight := horzEdge.CurrX;
2424 e := horzEdge.NextInAEL;
2425 while assigned(e) and (e <> maxPair) do
2426 e := e.NextInAEL;
2427 isLeftToRight := assigned(e);
2428 end
2429 else if horzEdge.CurrX < horzEdge.Top.X then
2430 begin
2431 horzLeft := horzEdge.CurrX;
2432 horzRight := horzEdge.Top.X;
2433 isLeftToRight := true;
2434 end else
2435 begin
2436 horzLeft := horzEdge.Top.X;
2437 horzRight := horzEdge.CurrX;
2438 isLeftToRight := false;
2439 end;
2440 end;
2441 //------------------------------------------------------------------------
2442
2443begin
2444(*******************************************************************************
2445* Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or *
2446* bottom of a scanbeam) are processed as if layered. The order in which HEs *
2447* are processed doesn't matter. HEs intersect with the bottom vertices of *
2448* other HEs [#] and with non-horizontal edges [*]. Once these intersections *
2449* are completed, intermediate HEs are 'promoted' to the next edge in their *
2450* bounds, and they in turn may be intersected [%] by other HEs. *
2451* *
2452* eg: 3 horizontals at a scanline: / | / / *
2453* | / | (HE3) o=========%==========o *
2454* o=======o (HE2) / | / / *
2455* o============#=========*======*========#=========o (HE1) *
2456* / | / | / *
2457*******************************************************************************)
2458
2459 // with closed paths, simplify consecutive horizontals into a 'single' edge
2460 if not IsOpen(horzEdge) then
2461 begin
2462 pt := horzEdge.Bot;
2463 while not IsMaxima(horzEdge) and
2464 (NextVertex(horzEdge).Pt.Y = pt.Y) do
2465 UpdateEdgeIntoAEL(horzEdge);
2466 horzEdge.Bot := pt;
2467 horzEdge.CurrX := pt.X;
2468 // update Dx in case of direction change ...
2469 if horzEdge.Bot.X < horzEdge.Top.X then
2470 horzEdge.Dx := NegInfinity else
2471 horzEdge.Dx := Infinity;
2472 end;
2473
2474 maxPair := nil;
2475 if IsMaxima(horzEdge) and (not IsOpen(horzEdge) or
2476 ([vfOpenStart, vfOpenEnd] * horzEdge.vertTop.flags = [])) then
2477 maxPair := GetMaximaPair(horzEdge);
2478
2479 ResetHorzDirection;
2480 if IsHotEdge(horzEdge) then
2481 AddOutPt(horzEdge, Point64(horzEdge.CurrX, horzEdge.Bot.Y));
2482
2483 while true do // loops through consec. horizontal edges (if open)
2484 begin
2485 isMax := IsMaxima(horzEdge);
2486 if isLeftToRight then
2487 e := horzEdge.NextInAEL else
2488 e := horzEdge.PrevInAEL;
2489
2490 while assigned(e) do
2491 begin
2492 // Break if we've gone past the end of the horizontal ...
2493 if (isLeftToRight and (e.CurrX > horzRight)) or
2494 (not isLeftToRight and (e.CurrX < horzLeft)) then Break;
2495 // or if we've got to the end of an intermediate horizontal edge ...
2496 if (E.CurrX = horzEdge.Top.X) and not isMax and not IsHorizontal(e) then
2497 begin
2498 pt := NextVertex(horzEdge).Pt;
2499 if(isLeftToRight and (TopX(E, pt.Y) >= pt.X)) or
2500 (not isLeftToRight and (TopX(E, pt.Y) <= pt.X)) then Break;
2501 end;
2502
2503 if (e = maxPair) then
2504 begin
2505 if IsHotEdge(horzEdge) then
2506 begin
2507 if isLeftToRight then
2508 AddLocalMaxPoly(horzEdge, e, horzEdge.Top) else
2509 AddLocalMaxPoly(e, horzEdge, horzEdge.Top);
2510 end;
2511 DeleteFromAEL(e);
2512 DeleteFromAEL(horzEdge);
2513 Exit;
2514 end;
2515
2516 pt := Point64(e.CurrX, horzEdge.Bot.Y);
2517 if (isLeftToRight) then
2518 begin
2519 IntersectEdges(horzEdge, e, pt);
2520 SwapPositionsInAEL(horzEdge, e);
2521 e := horzEdge.NextInAEL;
2522 end else
2523 begin
2524 IntersectEdges(e, horzEdge, pt);
2525 SwapPositionsInAEL(e, horzEdge);
2526 e := horzEdge.PrevInAEL;
2527 end;
2528 end;
2529
2530 // check if we've finished with (consecutive) horizontals ...
2531 if isMax or (NextVertex(horzEdge).Pt.Y <> horzEdge.Top.Y) then Break;
2532
2533 // still more horizontals in bound to process ...
2534 UpdateEdgeIntoAEL(horzEdge);
2535 ResetHorzDirection;
2536
2537 if IsOpen(horzEdge) then
2538 begin
2539 if IsMaxima(horzEdge) then maxPair := GetMaximaPair(horzEdge);
2540 if IsHotEdge(horzEdge) then AddOutPt(horzEdge, horzEdge.Bot);
2541 end;
2542 end;
2543
2544 if IsHotEdge(horzEdge) then
2545 AddOutPt(horzEdge, horzEdge.Top);
2546
2547 if not IsOpen(horzEdge) then
2548 UpdateEdgeIntoAEL(horzEdge) // this is the end of an intermediate horiz.
2549 else if not IsMaxima(horzEdge) then
2550 UpdateEdgeIntoAEL(horzEdge)
2551 else if not assigned(maxPair) then // ie open at top
2552 DeleteFromAEL(horzEdge)
2553 else if IsHotEdge(horzEdge) then
2554 AddLocalMaxPoly(horzEdge, maxPair, horzEdge.Top)
2555 else
2556 begin
2557 DeleteFromAEL(maxPair); DeleteFromAEL(horzEdge);
2558 end;
2559
2560end;
2561//------------------------------------------------------------------------------
2562
2563procedure TClipper.DoTopOfScanbeam(Y: Int64);
2564var
2565 e: PActive;
2566begin
2567 FSel := nil; // FSel is reused to flag horizontals (see PushHorz below)
2568 e := FActives;
2569 while Assigned(e) do
2570 begin
2571 // nb: 'e' will never be horizontal here
2572 if (e.Top.Y = Y) then
2573 begin
2574 // the following helps to avoid micro self-intersections
2575 // with negligible impact on performance ...
2576 e.CurrX := e.Top.X;
2577 if assigned(e.PrevInAEL) and (e.PrevInAEL.CurrX = e.CurrX) and
2578 (e.PrevInAEL.Bot.Y <> Y) and IsHotEdge(e.PrevInAEL) then
2579 AddOutPt(e.PrevInAEL, e.Top);
2580 if assigned(e.NextInAEL) and (e.NextInAEL.CurrX = e.CurrX) and
2581 (e.NextInAEL.Top.Y <> Y) and IsHotEdge(e.NextInAEL) then
2582 AddOutPt(e.NextInAEL, e.Top);
2583
2584 if IsMaxima(e) then
2585 begin
2586 e := DoMaxima(e); // TOP OF BOUND (MAXIMA)
2587 Continue;
2588 end else
2589 begin
2590 // INTERMEDIATE VERTEX ...
2591 UpdateEdgeIntoAEL(e);
2592 if IsHotEdge(e) then AddOutPt(e, e.Bot);
2593 if IsHorizontal(e) then
2594 PushHorz(e); // horizontals are processed later
2595 end;
2596 end;
2597 e := e.NextInAEL;
2598 end;
2599end;
2600//------------------------------------------------------------------------------
2601
2602function TClipper.DoMaxima(e: PActive): PActive;
2603var
2604 eNext, ePrev, eMaxPair: PActive;
2605begin
2606 ePrev := e.PrevInAEL;
2607 eNext := e.NextInAEL;
2608 Result := eNext;
2609
2610 if IsOpen(e) and ([vfOpenStart, vfOpenEnd] * e.vertTop.flags <> []) then
2611 begin
2612 if IsHotEdge(e) then AddOutPt(e, e.Top);
2613 if not IsHorizontal(e) then
2614 begin
2615 if IsHotEdge(e) then TerminateHotOpen(e);
2616 DeleteFromAEL(e);
2617 end;
2618 Exit;
2619 end else
2620 begin
2621 eMaxPair := GetMaximaPair(e);
2622 if not assigned(eMaxPair) then Exit; // EMaxPair is a horizontal ...
2623 end;
2624
2625 // only non-horizontal maxima here.
2626 // process any edges between maxima pair ...
2627 while (eNext <> eMaxPair) do
2628 begin
2629 IntersectEdges(e, eNext, e.Top);
2630 SwapPositionsInAEL(e, eNext);
2631 eNext := e.NextInAEL;
2632 end;
2633
2634 if IsOpen(e) then
2635 begin
2636 if IsHotEdge(e) then
2637 begin
2638 if assigned(eMaxPair) then
2639 AddLocalMaxPoly(e, eMaxPair, e.Top) else
2640 AddOutPt(e, e.Top);
2641 end;
2642 if assigned(eMaxPair) then
2643 DeleteFromAEL(eMaxPair);
2644 DeleteFromAEL(e);
2645
2646 if assigned(ePrev) then
2647 Result := ePrev.NextInAEL else
2648 Result := FActives;
2649 Exit;
2650 end;
2651
2652 // here E.NextInAEL == ENext == EMaxPair ...
2653 if IsHotEdge(e) then
2654 AddLocalMaxPoly(e, eMaxPair, e.Top);
2655
2656 DeleteFromAEL(e);
2657 DeleteFromAEL(eMaxPair);
2658 if assigned(ePrev) then
2659 Result := ePrev.NextInAEL else
2660 Result := FActives;
2661end;
2662//------------------------------------------------------------------------------
2663
2664function TClipper.BuildResult(out closedPaths,
2665 openPaths: TArrayOfArrayOfFloatPoint): Boolean;
2666var
2667 i, j, cntClosed, cntOpen: Integer;
2668 outRec: TOutRec;
2669begin
2670 try
2671 cntClosed := 0; cntOpen := 0;
2672 SetLength(closedPaths, FOutRecList.Count);
2673 SetLength(openPaths, FOutRecList.Count);
2674 for i := 0 to FOutRecList.Count -1 do
2675 begin
2676 outRec := FOutRecList[i];
2677 if not assigned(outRec.Pts) then Continue;
2678
2679 if IsOpen(outRec) then
2680 begin
2681 openPaths[cntOpen] := BuildPath(outRec.Pts);
2682 if length(openPaths[cntOpen]) > 1 then inc(cntOpen);
2683 end else
2684 begin
2685 closedPaths[cntClosed] := BuildPath(outRec.Pts);
2686 j := high(closedPaths[cntClosed]);
2687 if (j > 1) and PointsEqual(closedPaths[cntClosed][0],
2688 closedPaths[cntClosed][j]) then
2689 setlength(closedPaths[cntClosed], j);
2690 if j > 1 then inc(cntClosed);
2691 end;
2692 end;
2693 SetLength(closedPaths, cntClosed);
2694 SetLength(openPaths, cntOpen);
2695 Result := true;
2696 except
2697 Result := false;
2698 end;
2699end;
2700//------------------------------------------------------------------------------
2701
2702function TClipper.GetBounds: TFloatRect;
2703var
2704 i: Integer;
2705 v, vStart: PVertex;
2706begin
2707 if FVertexList.Count = 0 then
2708 Result := FloatRect(0, 0, 0, 0)
2709 else
2710 with PVertex(FVertexList[0]).Pt do
2711 Result := FloatRect(X, Y, X, Y);
2712 for i := 0 to FVertexList.Count -1 do
2713 begin
2714 vStart := FVertexList[i];
2715 v := vStart;
2716 repeat
2717 if v.Pt.X < Result.Left then Result.Left := v.Pt.X
2718 else if v.Pt.X > Result.Right then Result.Right := v.Pt.X;
2719 if v.Pt.Y < Result.Top then Result.Top := v.Pt.Y
2720 else if v.Pt.Y > Result.Bottom then Result.Bottom := v.Pt.Y;
2721 v := v.next;
2722 until v = vStart;
2723 end;
2724end;
2725
2726//------------------------------------------------------------------------------
2727// Miscellaneous ClipperOffset support functions
2728//------------------------------------------------------------------------------
2729
2730const
2731 MinFloat = -3.49E38;
2732 MaxFloat = 3.49E38;
2733
2734procedure AppendPath(var paths: TArrayOfArrayOfFloatPoint;
2735 const extra: TArrayOfFloatPoint);
2736var
2737 len: Integer;
2738begin
2739 len := length(paths);
2740 SetLength(paths, len +1);
2741 paths[len] := extra;
2742end;
2743//------------------------------------------------------------------------------
2744
2745procedure StripDuplicates(var path: TArrayOfFloatPoint);
2746var
2747 i, len: integer;
2748begin
2749 len := length(path);
2750 i := 1;
2751 while i < len do
2752 begin
2753 if (path[i].X = path[i-1].X) and (path[i].Y = path[i-1].Y) then
2754 begin
2755 dec(len);
2756 if (i < len) then
2757 Move(path[i+1], path[i], (len-i)*SizeOf(TFloatPoint));
2758 SetLength(path, len);
2759 end else
2760 inc(i);
2761 end;
2762end;
2763//------------------------------------------------------------------------------
2764
2765function ReversePath(const path: TArrayOfFloatPoint): TArrayOfFloatPoint;
2766var
2767 i, highI: Integer;
2768begin
2769 highI := high(path);
2770 SetLength(Result, highI +1);
2771 for i := 0 to highI do
2772 Result[i] := path[highI - i];
2773end;
2774//------------------------------------------------------------------------------
2775
2776function DistanceSqr(const pt1, pt2: TFloatPoint): TFloat;
2777begin
2778 Result := (pt1.X - pt2.X)*(pt1.X - pt2.X) + (pt1.Y - pt2.Y)*(pt1.Y - pt2.Y);
2779end;
2780//------------------------------------------------------------------------------
2781
2782function GetUnitNormal(const pt1, pt2: TFloatPoint): TFloatPoint;
2783var
2784 dx, dy, inverseHypot: Double;
2785begin
2786 if PointsEqual(pt1, pt2) then
2787 begin
2788 Result.X := 0;
2789 Result.Y := 0;
2790 Exit;
2791 end;
2792 dx := (pt2.X - pt1.X);
2793 dy := (pt2.Y - pt1.Y);
2794 inverseHypot := 1 / Hypot(dx, dy);
2795 dx := dx * inverseHypot;
2796 dy := dy * inverseHypot;
2797 Result.X := dy;
2798 Result.Y := -dx
2799end;
2800
2801//------------------------------------------------------------------------------
2802// TClipperOffset methods
2803//------------------------------------------------------------------------------
2804
2805constructor TClipperOffset.Create(MiterLimit: Double; ArcTolerance: Double);
2806begin
2807 inherited Create;
2808 if MiterLimit = 0 then MiterLimit := 2;
2809 FMiterLimit := MiterLimit;
2810 FArcTolerance := ArcTolerance;
2811end;
2812//------------------------------------------------------------------------------
2813
2814destructor TClipperOffset.Destroy;
2815begin
2816 Clear;
2817 inherited;
2818end;
2819//------------------------------------------------------------------------------
2820
2821procedure TClipperOffset.Clear;
2822begin
2823 FPathsIn := nil;
2824 FNorms := nil;
2825 FSolution := nil;
2826end;
2827//------------------------------------------------------------------------------
2828
2829procedure TClipperOffset.AddPath(const path: TArrayOfFloatPoint);
2830begin
2831 if assigned(path) then
2832 AppendPath(FPathsIn, path);
2833end;
2834//------------------------------------------------------------------------------
2835
2836procedure TClipperOffset.AddPaths(const paths: TArrayOfArrayOfFloatPoint);
2837var
2838 i: Integer;
2839begin
2840 for i := 0 to High(paths) do
2841 AddPath(paths[i]);
2842end;
2843//------------------------------------------------------------------------------
2844
2845function TClipperOffset.GetLowestPolygonIdx: integer;
2846var
2847 i,j, len: Integer;
2848 pt: TFloatPoint;
2849 p: TArrayOfFloatPoint;
2850begin
2851 result := -1;
2852 pt := FloatPoint(MaxFloat, MinFloat);
2853 for i := 0 to high(FPathsIn) do
2854 begin
2855 if FPathsIn[i] = nil then
2856 Continue;
2857 p := FPathsIn[i];
2858 len := length(p);
2859 for j := 0 to len -1 do
2860 begin
2861 if (p[j].Y < pt.Y) then
2862 continue;
2863 if (p[j].Y > pt.Y) or (p[j].X < pt.X) then
2864 begin
2865 pt := p[j];
2866 result := i;
2867 end;
2868 end;
2869 end;
2870end;
2871//------------------------------------------------------------------------------
2872
2873function TClipperOffset.CheckPaths: boolean;
2874var
2875 i,len, minLen: Integer;
2876 openPaths: Boolean;
2877begin
2878 Result := False;
2879 openPaths := not (FEndType in [etPolygon, etOpenJoined]);
2880 if openPaths then minLen := 1 else minLen := 3;
2881 for i := 0 to high(FPathsIn) do
2882 begin
2883 StripDuplicates(FPathsIn[i]);
2884 len := length(FPathsIn[i]);
2885 if not openPaths and (len > 1) and
2886 PointsEqual(FPathsIn[i][0], FPathsIn[i][len-1]) then
2887 begin
2888 setlength(FPathsIn[i], len -1);
2889 dec(len);
2890 end;
2891 if len < minLen then
2892 FPathsIn[i] := nil
2893 else
2894 Result := True;
2895 end;
2896end;
2897//------------------------------------------------------------------------------
2898
2899procedure TClipperOffset.OffsetPaths;
2900var
2901 i, len: Integer;
2902 arcTol, absDelta, steps: Double;
2903 tmpEndType: TEndType;
2904begin
2905 absDelta := Abs(FDelta);
2906 len := length(FPathsIn);
2907
2908 // if a Zero offset, then simply copy paths to FSolution and return ...
2909 if absDelta < Tolerance then
2910 begin
2911 FSolutionLen := 0;
2912 SetLength(FSolution, len);
2913 for i := 0 to high(FPathsIn) do
2914 if assigned(FPathsIn[i]) then
2915 begin
2916 FSolution[FSolutionLen] := FPathsIn[i];
2917 inc(FSolutionLen);
2918 end;
2919 SetLength(FSolution, FSolutionLen);
2920 Exit;
2921 end;
2922
2923 // FMiterLimit: see offset_triginometry3.svg
2924 if FMiterLimit > 1 then FMiterLim := 2/(sqr(FMiterLimit))
2925 else FMiterLim := 2;
2926
2927 if (FArcTolerance <= DefaultArcFrac) then
2928 arcTol := DefaultArcFrac else
2929 arcTol := FArcTolerance;
2930
2931 if (FJoinType in [jtRound, jtRoundEx]) or (FEndType = etOpenRound) then
2932 begin
2933 // get steps per 360 degrees (see offset_triginometry2.svg)
2934 steps := PI / ArcCos(1 - arcTol / absDelta);
2935
2936 // avoid excessive precision ...
2937 if (steps > absDelta * Pi) then steps := absDelta * Pi;
2938 FStepsPerRad := steps / Two_Pi;
2939 Math.SinCos(Two_Pi / steps, FStepSizeSin, FStepSizeCos);
2940 if FDelta < 0 then FStepSizeSin := -FStepSizeSin;
2941 end;
2942
2943 if (FEndType = etOpenJoined) then
2944 SetLength(FSolution, len *2) else
2945 SetLength(FSolution, len);
2946
2947 FSolutionLen := 0;
2948 for i := 0 to len -1 do
2949 begin
2950 FPathIn := FPathsIn[i];
2951 if FPathIn = nil then Continue;
2952
2953 FPathOutLen := 0;
2954 FPathOut := nil;
2955
2956 if Length(FPathIn) = 1 then
2957 begin
2958 // a simple workaround using OffsetOpenPath to construct
2959 // either a circle or a square point offset ...
2960 tmpEndType := FEndType;
2961 if FEndType = etOpenButt then FEndType := etOpenSquare;
2962 SetLength(FPathIn, 2);
2963 FPathIn[1] := FPathIn[0];
2964 SetLength(FNorms, 2);
2965 FNorms[0] := FloatPoint(1,0);
2966 OffsetOpenPath;
2967 FEndType := tmpEndType;
2968 end else
2969 begin
2970 BuildNormals;
2971 if FEndType = etPolygon then
2972 OffsetPolygon
2973 else if FEndType = etOpenJoined then
2974 OffsetOpenJoined
2975 else
2976 OffsetOpenPath;
2977 end;
2978
2979 if FPathOutLen = 0 then Continue;
2980
2981 SetLength(FPathOut, FPathOutLen);
2982 FSolution[FSolutionLen] := FPathOut;
2983 Inc(FSolutionLen);
2984 end;
2985 SetLength(FSolution, FSolutionLen);
2986end;
2987//------------------------------------------------------------------------------
2988
2989procedure TClipperOffset.BuildNormals;
2990var
2991 i, len: integer;
2992begin
2993 len := Length(FPathIn);
2994 SetLength(FNorms, len);
2995 for i := 0 to len-2 do
2996 FNorms[i] := GetUnitNormal(FPathIn[i], FPathIn[i+1]);
2997 FNorms[len -1] := GetUnitNormal(FPathIn[len -1], FPathIn[0]);
2998end;
2999//------------------------------------------------------------------------------
3000
3001procedure TClipperOffset.ReverseNormals;
3002var
3003 i, highI: integer;
3004 tmp: TFloatPoint;
3005begin
3006 FNorms := ReversePath(FNorms);
3007 highI := high(FNorms);
3008 tmp := FNorms[0];
3009 for i := 1 to highI do
3010 begin
3011 FNorms[i-1].X := -FNorms[i].X;
3012 FNorms[i-1].Y := -FNorms[i].Y;
3013 end;
3014 FNorms[highI].X := -tmp.X;
3015 FNorms[highI].Y := -tmp.Y;
3016end;
3017//------------------------------------------------------------------------------
3018
3019procedure TClipperOffset.OffsetPolygon;
3020var
3021 i,j: integer;
3022begin
3023 j := high(FPathIn);
3024 for i := 0 to high(FPathIn) do
3025 begin
3026 OffsetPoint(i, j);
3027 j := i;
3028 end;
3029end;
3030//------------------------------------------------------------------------------
3031
3032procedure TClipperOffset.OffsetOpenJoined;
3033begin
3034 OffsetPolygon;
3035 FPathIn := ReversePath(FPathIn);
3036
3037 SetLength(FPathOut, FPathOutLen);
3038 FSolution[FSolutionLen] := FPathOut;
3039 Inc(FSolutionLen);
3040 FPathOutLen := 0;
3041 FPathOut := nil;
3042
3043 ReverseNormals;
3044 OffsetPolygon;
3045end;
3046//------------------------------------------------------------------------------
3047
3048procedure TClipperOffset.OffsetOpenPath;
3049
3050 procedure DoButtCap(highI: integer);
3051 begin
3052 AddPoint(FloatPoint(FPathIn[highI].X + FNorms[highI-1].X *FDelta,
3053 FPathIn[highI].Y + FNorms[highI-1].Y * FDelta));
3054 AddPoint(FloatPoint(FPathIn[highI].X - FNorms[highI-1].X *FDelta,
3055 FPathIn[highI].Y - FNorms[highI-1].Y * FDelta));
3056 end;
3057
3058 procedure DoSquareCap(highI: integer; toStart: Boolean);
3059 var
3060 pt: TFloatPoint;
3061 const
3062 sc: array[boolean] of integer = (1, -1);
3063 begin
3064 pt := FloatPoint(FPathIn[highI].X + FNorms[highI-1].X *FDelta,
3065 FPathIn[highI].Y + FNorms[highI-1].Y * FDelta);
3066 AddPoint(pt);
3067 AddPoint(FloatPoint(pt.X - FNorms[highI-1].Y *FDelta,
3068 pt.Y - FNorms[highI-1].X * FDelta * sc[true]));
3069 pt := FloatPoint(FPathIn[highI].X - FNorms[highI-1].X *FDelta,
3070 FPathIn[highI].Y - FNorms[highI-1].Y * FDelta);
3071 AddPoint(FloatPoint(pt.X - FNorms[highI-1].Y *FDelta,
3072 pt.Y - FNorms[highI-1].X * FDelta * sc[true]));
3073 AddPoint(pt);
3074 end;
3075
3076 procedure DoRoundCap(highI: integer); // 180 degrees
3077 var
3078 i: integer;
3079 steps: Integer;
3080 pt: TFloatPoint;
3081 begin
3082 steps := Round(FStepsPerRad * PI);
3083 pt.X := FNorms[highI-1].X * FDelta;
3084 pt.Y := FNorms[highI-1].Y * FDelta;
3085 for i := 1 to steps do
3086 begin
3087 AddPoint(FloatPoint(FPathIn[highI].X + pt.X, FPathIn[highI].Y + pt.Y));
3088 pt := FloatPoint(pt.X * FStepSizeCos - FStepSizeSin * pt.Y,
3089 pt.X * FStepSizeSin + pt.Y * FStepSizeCos);
3090 end;
3091 end;
3092
3093var
3094 i,j, highI: integer;
3095begin
3096 highI := high(FPathIn);
3097 j := 0;
3098 for i := 1 to highI -1 do
3099 begin
3100 OffsetPoint(i, j);
3101 j := i;
3102 end;
3103
3104 // cap the end first ...
3105 case FEndType of
3106 etOpenButt: DoButtCap(highI);
3107 etOpenRound: DoRoundCap(highI);
3108 else DoSquareCap(highI, false);
3109 end;
3110
3111 FPathIn := ReversePath(FPathIn);
3112 ReverseNormals;
3113 j := 0;
3114 for i := 0 to highI -1 do
3115 begin
3116 OffsetPoint(i, j);
3117 j := i;
3118 end;
3119
3120 // now cap the start ...
3121 case FEndType of
3122 etOpenButt: DoButtCap(highI);
3123 etOpenRound: DoRoundCap(highI);
3124 else DoSquareCap(highI, true);
3125 end;
3126end;
3127//------------------------------------------------------------------------------
3128
3129procedure TClipperOffset.Execute(delta: Double; jt: TJoinType; et: TEndType;
3130 out solution: TArrayOfArrayOfFloatPoint);
3131var
3132 negate: Boolean;
3133 lowestIdx: integer;
3134begin
3135 solution := nil;
3136 if length(FPathsIn) = 0 then Exit;
3137 FJoinType := jt;
3138 FEndType := et;
3139
3140 if (not CheckPaths) then
3141 exit;
3142
3143 negate := false;
3144 if (et = etPolygon) then
3145 begin
3146 // the lowermost polygon must be an outer polygon. So we can use that as the
3147 // designated orientation for outer polygons (needed for tidy-up clipping)
3148 lowestIdx := GetLowestPolygonIdx;
3149 negate := (Area(FPathsIn[lowestIdx]) < 0);
3150 // if polygon orientations are reversed, then 'negate' ...
3151 // if negate then FDelta := FDelta;
3152 end;
3153
3154 if FEndType <> etPolygon then
3155 FDelta := Abs(delta) else
3156 FDelta := delta;
3157 OffsetPaths;
3158
3159// solution := FSolution;
3160
3161 // clean up self-intersections ...
3162 with TClipper.Create do
3163 try
3164 AddPaths(FSolution, ptSubject);
3165 if negate then
3166 Execute(ctUnion, frNegative, solution) else
3167 Execute(ctUnion, frPositive, solution);
3168 finally
3169 free;
3170 end;
3171end;
3172//------------------------------------------------------------------------------
3173
3174procedure TClipperOffset.AddPoint(const pt: TFloatPoint);
3175const
3176 BuffLength = 32;
3177begin
3178 if FPathOutLen = length(FPathOut) then
3179 SetLength(FPathOut, FPathOutLen + BuffLength);
3180 if (FPathOutLen > 0) and PointsEqual(FPathOut[FPathOutLen-1], pt) then Exit;
3181 FPathOut[FPathOutLen] := pt;
3182 Inc(FPathOutLen);
3183end;
3184//------------------------------------------------------------------------------
3185
3186procedure TClipperOffset.DoSquare(j, k: Integer);
3187begin
3188 // Two vertices, one using the prior offset's (k) normal one the current (j).
3189 // Do a 'normal' offset (by delta) and then another by 'de-normaling' the
3190 // normal hence parallel to the direction of the respective edges.
3191 if FDelta > 0 then
3192 begin
3193 AddPoint(FloatPoint(
3194 FPathIn[j].X + FDelta * (FNorms[k].X - FNorms[k].Y),
3195 FPathIn[j].Y + FDelta * (FNorms[k].Y + FNorms[k].X)));
3196 AddPoint(FloatPoint(
3197 FPathIn[j].X + FDelta * (FNorms[j].X + FNorms[j].Y),
3198 FPathIn[j].Y + FDelta * (FNorms[j].Y - FNorms[j].X)));
3199 end else
3200 begin
3201 AddPoint(FloatPoint(
3202 FPathIn[j].X + FDelta * (FNorms[k].X + FNorms[k].Y),
3203 FPathIn[j].Y + FDelta * (FNorms[k].Y - FNorms[k].X)));
3204 AddPoint(FloatPoint(
3205 FPathIn[j].X + FDelta * (FNorms[j].X - FNorms[j].Y),
3206 FPathIn[j].Y + FDelta * (FNorms[j].Y + FNorms[j].X)));
3207 end;
3208end;
3209//------------------------------------------------------------------------------
3210
3211procedure TClipperOffset.DoMiter(j, k: Integer; cosAplus1: Double);
3212var
3213 q: Double;
3214begin
3215 // see offset_triginometry4.svg
3216 q := FDelta / cosAplus1; // 0 < cosAplus1 <= 2
3217 AddPoint(FloatPoint(FPathIn[j].X + (FNorms[k].X + FNorms[j].X)*q,
3218 FPathIn[j].Y + (FNorms[k].Y + FNorms[j].Y)*q));
3219end;
3220//------------------------------------------------------------------------------
3221
3222procedure TClipperOffset.DoRound(j, k: Integer);
3223var
3224 i, m,n, steps: Integer;
3225 a, delta, sinA, cosA: Double;
3226 pt, pt2, pt3: TFloatPoint;
3227begin
3228 sinA := FNorms[k].X * FNorms[j].Y - FNorms[k].Y * FNorms[j].X;
3229 cosA := FNorms[j].X * FNorms[k].X + FNorms[j].Y * FNorms[k].Y;
3230 a := ArcTan2(sinA, cosA);
3231 steps := Round(FStepsPerRad * Abs(a));
3232
3233 if (FDelta * sinA < 0) then // ie concave
3234 begin
3235 a := FDelta / (cosA +1);
3236 if (j = 0) then m := high(FPathIn) else m := j -1;
3237 if j = high(FPathIn) then n := 0 else n := j +1;
3238
3239 // offset pt of concave vertex ...
3240 pt.X := round(FPathIn[j].X + (FNorms[k].X + FNorms[j].X)*a);
3241 pt.Y := round(FPathIn[j].Y + (FNorms[k].Y + FNorms[j].Y)*a);
3242
3243 a := Min(DistanceSqr(FPathIn[m], FPathIn[j]),
3244 DistanceSqr(FPathIn[n], FPathIn[j]));
3245
3246 // there's no space to draw anything ...
3247 if DistanceSqr(pt, FPathIn[j]) > a then
3248 begin
3249 // get the perpendicular offsets from pt2 ...
3250 // this creates a self-intersection that'll be clipped later
3251 pt2.X := round(FPathIn[j].X + FNorms[k].X * FDelta);
3252 pt2.Y := round(FPathIn[j].Y + FNorms[k].Y * FDelta);
3253 pt3.X := round(FPathIn[j].X + FNorms[j].X * FDelta);
3254 pt3.Y := round(FPathIn[j].Y + FNorms[j].Y * FDelta);
3255 AddPoint(pt2);
3256 AddPoint(pt3);
3257 Exit;
3258 end;
3259
3260 a := Sqrt(a);
3261 // get the point on each edge being the distance of the shortest edge
3262 // from the concave vertex. (nb: unit normals to unit vectors here)
3263 pt2.X := round(FPathIn[j].X + FNorms[k].Y * a);
3264 pt2.Y := round(FPathIn[j].Y - FNorms[k].X * a);
3265 pt3.X := round(FPathIn[j].X - FNorms[j].Y * a);
3266 pt3.Y := round(FPathIn[j].Y + FNorms[j].X * a);
3267
3268 // now FDelta offset these points ...
3269 pt2.X := round(pt2.X + FNorms[k].X * FDelta);
3270 pt2.Y := round(pt2.Y + FNorms[k].Y * FDelta);
3271 pt3.X := round(pt3.X + FNorms[j].X * FDelta);
3272 pt3.Y := round(pt3.Y + FNorms[j].Y * FDelta);
3273
3274 if DistanceSqr(pt2, pt3) < Sqr(FDelta *2/MiterLimit) then
3275 delta := Sqrt(DistanceSqr(pt2, pt3))/2 else
3276 delta := FDelta/MiterLimit;
3277
3278 a := (delta + FDelta) / (cosA +1);
3279 pt.X := round(FPathIn[j].X + (FNorms[k].X + FNorms[j].X)*a);
3280 pt.Y := round(FPathIn[j].Y + (FNorms[k].Y + FNorms[j].Y)*a);
3281
3282 pt2.X := -FNorms[k].X * delta;
3283 pt2.Y := -FNorms[k].Y * delta;
3284 AddPoint(FloatPoint(pt.X + pt2.X, pt.Y + pt2.Y));
3285 for i := 1 to steps -1 do
3286 begin
3287 pt2 := FloatPoint(pt2.X * FStepSizeCos + FStepSizeSin * pt2.Y,
3288 -pt2.X * FStepSizeSin + pt2.Y * FStepSizeCos);
3289 AddPoint(FloatPoint(pt.X + pt2.X, pt.Y + pt2.Y));
3290 end;
3291 end else
3292 begin
3293 // a convex vertex ...
3294 pt := FPathIn[j];
3295 pt2.X := FNorms[k].X * FDelta;
3296 pt2.Y := FNorms[k].Y * FDelta;
3297 AddPoint(FloatPoint(pt.X + pt2.X, pt.Y + pt2.Y));
3298 for i := 1 to steps -1 do
3299 begin
3300 pt2 := FloatPoint(pt2.X * FStepSizeCos - FStepSizeSin * pt2.Y,
3301 pt2.X * FStepSizeSin + pt2.Y * FStepSizeCos);
3302 AddPoint(FloatPoint(pt.X + pt2.X, pt.Y + pt2.Y));
3303 end;
3304 end;
3305end;
3306//------------------------------------------------------------------------------
3307
3308procedure TClipperOffset.OffsetPoint(j,k: Integer);
3309var
3310 sinA, cosA: Double;
3311begin
3312 // A: angle between adjoining edges (on left side WRT winding direction).
3313 // A == 0 deg (or A == 360 deg): collinear edges heading in same direction
3314 // A == 180 deg: collinear edges heading in opposite directions (ie a 'spike')
3315 // sin(A) < 0: convex on left.
3316 // cos(A) > 0: angles on both left and right sides > 90 degrees
3317 sinA := (FNorms[k].X * FNorms[j].Y - FNorms[j].X * FNorms[k].Y);
3318 cosA := (FNorms[j].X * FNorms[k].X + FNorms[j].Y * FNorms[k].Y);
3319
3320 if (Abs(sinA * FDelta) < 1.0) then // angle is close to 0 or 180 deg.
3321 begin
3322 if (cosA > 0) then // given condition above the angle is approaching 0 deg.
3323 begin
3324 if FJoinType = jtRoundEx then
3325 DoRound(j, k)
3326 else
3327 // with angles approaching 0 deg collinear (whether concave or convex),
3328 // offsetting with two or more vertices (that would be so close together)
3329 // occasionally causes tiny self-intersections due to rounding.
3330 // So we offset with just a single vertex here ...
3331 AddPoint(FloatPoint(FPathIn[j].X + FNorms[k].X * FDelta,
3332 FPathIn[j].Y + FNorms[k].Y * FDelta));
3333 Exit;
3334 end;
3335 // else angle must be approaching 180 deg.
3336 end
3337 else if (sinA > 1.0) then sinA := 1.0
3338 else if (sinA < -1.0) then sinA := -1.0;
3339
3340 if (FJoinType = jtRoundEx) then
3341 begin
3342 DoRound(j, k)
3343 end
3344 else if sinA * FDelta < 0 then // ie a concave offset
3345 begin
3346 AddPoint(FloatPoint(FPathIn[j].X + FNorms[k].X * FDelta,
3347 FPathIn[j].Y + FNorms[k].Y * FDelta));
3348 AddPoint(FPathIn[j]); // this improves clipping removal later
3349 AddPoint(FloatPoint(FPathIn[j].X + FNorms[j].X * FDelta,
3350 FPathIn[j].Y + FNorms[j].Y * FDelta));
3351 end
3352 else
3353 begin
3354 // convex offsets here ...
3355 case FJoinType of
3356 jtMiter:
3357 // see offset_triginometry3.svg
3358 if (1 + cosA < FMiterLim) then DoSquare(j, k)
3359 else DoMiter(j, k, 1 + cosA);
3360 jtSquare:
3361 // angles >= 90 deg. don't need squaring
3362 if cosA >= 0 then
3363 DoMiter(j, k, 1 + cosA) else
3364 DoSquare(j, k);
3365
3366 else DoRound(j, k);
3367 end;
3368 end;
3369end;
3370
3371//------------------------------------------------------------------------------
3372//------------------------------------------------------------------------------
3373
3374function InflatePaths(const paths: TArrayOfArrayOfFloatPoint;
3375 delta: Double; jt: TJoinType; et: TEndType;
3376 miterLimit: single): TArrayOfArrayOfFloatPoint;
3377begin
3378 with TClipperOffset.Create(miterLimit) do
3379 try
3380 AddPaths(paths);
3381 Execute(delta, jt, et, Result);
3382 finally
3383 free;
3384 end;
3385end;
3386//------------------------------------------------------------------------------
3387
3388end.
3389
Note: See TracBrowser for help on using the repository browser.