diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index 0e9c74798..df0641317 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -195,6 +195,7 @@ namespace Clipper2Lib { FillRule fillrule_ = FillRule::EvenOdd; FillRule fillpos = FillRule::Positive; int64_t bot_y_ = 0; + bool curr_x_at_top_ = false; // every active edge's curr_x is already TopX(top_y) bool minima_list_sorted_ = false; bool using_polytree_ = false; Active* actives_ = nullptr; @@ -226,7 +227,7 @@ namespace Clipper2Lib { inline void UpdateEdgeIntoAEL(Active *e); void IntersectEdges(Active &e1, Active &e2, const Point64& pt); inline void DeleteFromAEL(Active &e); - inline void AdjustCurrXAndCopyToSEL(const int64_t top_y); + inline bool AdjustCurrXAndCopyToSEL(const int64_t top_y); void DoIntersections(const int64_t top_y); void AddNewIntersectNode(Active &e1, Active &e2, const int64_t top_y); bool BuildIntersectList(const int64_t top_y); diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 6442c96ad..97ad51dc1 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -2099,10 +2099,13 @@ namespace Clipper2Lib { } - inline void ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y) + // returns true when the edges are still sorted by curr_x (ie no intersections) + inline bool ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y) { Active* e = actives_; sel_ = e; + bool sorted = true; + int64_t prev_x = std::numeric_limits::min(); while (e) { e->prev_in_sel = e->prev_in_ael; @@ -2110,9 +2113,13 @@ namespace Clipper2Lib { e->jump = e->next_in_sel; // it is safe to ignore 'joined' edges here because // if necessary they will be split in IntersectEdges() - e->curr_x = TopX(*e, top_y); + int64_t x = TopX(*e, top_y); + e->curr_x = x; + if (x < prev_x) sorted = false; + prev_x = x; e = e->next_in_ael; } + return sorted; } bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees) @@ -2335,6 +2342,7 @@ namespace Clipper2Lib { void ClipperBase::DoIntersections(const int64_t top_y) { + curr_x_at_top_ = false; if (BuildIntersectList(top_y)) { ProcessIntersectList(); @@ -2382,7 +2390,13 @@ namespace Clipper2Lib { //Calculate edge positions at the top of the current scanbeam, and from this //we will determine the intersections required to reach these new positions. - AdjustCurrXAndCopyToSEL(top_y); + //If the edges are still in order there are no intersections, so the merge + //sort (which always does log2(n) passes over the edges) can be skipped. + if (AdjustCurrXAndCopyToSEL(top_y)) + { + curr_x_at_top_ = true; + return false; + } //Find all edge intersections in the current scanbeam using a stable merge //sort that ensures only adjacent edges are intersecting. Intersect info is //stored in FIntersectList ready to be processed in ProcessIntersectList. @@ -2718,7 +2732,7 @@ namespace Clipper2Lib { PushHorz(*e); // horizontals are processed later } } - else // i.e. not the top of the edge + else if (!curr_x_at_top_) // i.e. not the top of the edge e->curr_x = TopX(*e, y); e = e->next_in_ael; diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 481e723af..39e3b1d48 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -367,6 +367,7 @@ public class ClipperBase private readonly OutPtPoolList _outPtPool; private int _currentLocMin; private long _currentBotY; + private bool _currXAtTop; // every active edge's curX is already TopX(topY) private bool _isSortedMinimaList; private bool _hasOpenPaths; internal bool _using_polytree; @@ -1855,11 +1856,14 @@ private Active NewActive() return ae; } + // returns true when the edges are still sorted by curX (ie no intersections) [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AdjustCurrXAndCopyToSEL(long topY) + private bool AdjustCurrXAndCopyToSEL(long topY) { Active? ae = _actives; _sel = ae; + bool sorted = true; + long prevX = long.MinValue; while (ae != null) { ae.prevInSEL = ae.prevInAEL; @@ -1867,10 +1871,14 @@ private void AdjustCurrXAndCopyToSEL(long topY) ae.jump = ae.nextInSEL; // it is safe to ignore 'joined' edges here because // if necessary they will be split in IntersectEdges() - ae.curX = TopX(ae, topY); + long x = TopX(ae, topY); + ae.curX = x; + if (x < prevX) sorted = false; + prevX = x; // NB don't update ae.curr.Y yet (see AddNewIntersectNode) ae = ae.nextInAEL; } + return sorted; } protected void ExecuteInternal(ClipType ct, FillRule fillRule) @@ -1903,6 +1911,7 @@ protected void ExecuteInternal(ClipType ct, FillRule fillRule) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DoIntersections(long topY) { + _currXAtTop = false; if (!BuildIntersectList(topY)) return; ProcessIntersectList(); DisposeIntersectNodes(); @@ -1984,7 +1993,13 @@ private bool BuildIntersectList(long topY) // Calculate edge positions at the top of the current scanbeam, and from this // we will determine the intersections required to reach these new positions. - AdjustCurrXAndCopyToSEL(topY); + // If the edges are still in order there are no intersections, so the merge + // sort (which always does log2(n) passes over the edges) can be skipped. + if (AdjustCurrXAndCopyToSEL(topY)) + { + _currXAtTop = true; + return false; + } // Find all edge intersections in the current scanbeam using a stable merge // sort that ensures only adjacent edges are intersecting. Intersect info is @@ -2331,7 +2346,7 @@ private void DoTopOfScanbeam(long y) if (IsHorizontal(ae)) PushHorz(ae); // horizontals are processed later } - else // i.e. not the top of the edge + else if (!_currXAtTop) // i.e. not the top of the edge ae.curX = TopX(ae, y); ae = ae.nextInAEL; diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index fba641eae..b5147375a 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -198,6 +198,7 @@ TScanLine = record TClipperBase = class {$IFDEF STRICT}strict{$ENDIF} private FBotY : Int64; + FCurrXAtTop : Boolean; // every active edge's currX is already TopX(topY) FScanLine : PScanLine; FCurrentLocMinIdx : Integer; FClipType : TClipType; @@ -242,7 +243,7 @@ TClipperBase = class procedure IntersectEdges(e1, e2: PActive; pt: TPoint64); procedure DeleteEdges(var e: PActive); procedure DeleteFromAEL(e: PActive); - procedure AdjustCurrXAndCopyToSEL(topY: Int64); + function AdjustCurrXAndCopyToSEL(topY: Int64): Boolean; procedure ConvertHorzSegsToJoins; procedure ProcessHorzJoins; procedure DoIntersections(const topY: Int64); @@ -2823,10 +2824,14 @@ procedure TClipperBase.DeleteFromAEL(e: PActive); end; //------------------------------------------------------------------------------ -procedure TClipperBase.AdjustCurrXAndCopyToSEL(topY: Int64); +function TClipperBase.AdjustCurrXAndCopyToSEL(topY: Int64): Boolean; var e: PActive; + x, prevX: Int64; begin + // returns true when the edges are still sorted by currX (ie no intersections) + Result := true; + prevX := Low(Int64); FSel := FActives; e := FActives; while Assigned(e) do @@ -2836,7 +2841,10 @@ procedure TClipperBase.AdjustCurrXAndCopyToSEL(topY: Int64); e.jump := e.nextInSEL; // it is safe to ignore 'joined' edges here because // if necessary they will be split in IntersectEdges() - e.currX := TopX(e, topY); + x := TopX(e, topY); + e.currX := x; + if x < prevX then Result := false; + prevX := x; e := e.nextInAEL; end; end; @@ -3133,6 +3141,7 @@ procedure TClipperBase.ProcessHorzJoins; procedure TClipperBase.DoIntersections(const topY: Int64); begin + FCurrXAtTop := false; if BuildIntersectList(topY) then try ProcessIntersectList; @@ -3221,7 +3230,13 @@ function TClipperBase.BuildIntersectList(const topY: Int64): Boolean; // Calculate edge positions at the top of the current scanbeam, and from this // we will determine the intersections required to reach these new positions. - AdjustCurrXAndCopyToSEL(topY); + // If the edges are still in order there are no intersections, so the merge + // sort (which always does log2(n) passes over the edges) can be skipped. + if AdjustCurrXAndCopyToSEL(topY) then + begin + FCurrXAtTop := true; + Exit; + end; // Find all edge intersections in the current scanbeam using a stable merge // sort that ensures only adjacent edges are intersecting. Intersect info is @@ -3604,7 +3619,8 @@ procedure TClipperBase.DoTopOfScanbeam(Y: Int64); if IsHorizontal(e) then PushHorz(e); end; - end else + end + else if not FCurrXAtTop then // i.e. not the top of the edge e.currX := TopX(e, Y); e := e.nextInAEL; end;