123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984 |
-
- #nullable enable
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Runtime.CompilerServices;
- namespace Clipper2Lib
- {
- [Flags]
- public enum PointInPolygonResult
- {
- IsOn = 0,
- IsInside = 1,
- IsOutside = 2
- };
- [Flags]
- internal enum VertexFlags
- {
- None = 0,
- OpenStart = 1,
- OpenEnd = 2,
- LocalMax = 4,
- LocalMin = 8
- };
- internal class Vertex
- {
- public readonly Point64 pt;
- public Vertex? next;
- public Vertex? prev;
- public VertexFlags flags;
- public Vertex(Point64 pt, VertexFlags flags, Vertex? prev)
- {
- this.pt = pt;
- this.flags = flags;
- next = null;
- this.prev = prev;
- }
- };
- internal readonly struct LocalMinima
- {
- public readonly Vertex vertex;
- public readonly PathType polytype;
- public readonly bool isOpen;
- public LocalMinima(Vertex vertex, PathType polytype, bool isOpen = false)
- {
- this.vertex = vertex;
- this.polytype = polytype;
- this.isOpen = isOpen;
- }
- public static bool operator ==(LocalMinima lm1, LocalMinima lm2)
- {
- return ReferenceEquals(lm1.vertex, lm2.vertex);
- }
- public static bool operator !=(LocalMinima lm1, LocalMinima lm2)
- {
- return !(lm1 == lm2);
- }
- public override bool Equals(object? obj)
- {
- return obj is LocalMinima minima && this == minima;
- }
- public override int GetHashCode()
- {
- return vertex.GetHashCode();
- }
- };
- // IntersectNode: a structure representing 2 intersecting edges.
- // Intersections must be sorted so they are processed from the largest
- // Y coordinates to the smallest while keeping edges adjacent.
- internal struct IntersectNode
- {
- public readonly Point64 pt;
- public readonly Active edge1;
- public readonly Active edge2;
- public IntersectNode(Point64 pt, Active edge1, Active edge2)
- {
- this.pt = pt;
- this.edge1 = edge1;
- this.edge2 = edge2;
- }
- };
- internal struct LocMinSorter : IComparer<LocalMinima>
- {
- public int Compare(LocalMinima locMin1, LocalMinima locMin2)
- {
- return locMin2.vertex.pt.Y.CompareTo(locMin1.vertex.pt.Y);
- }
- }
- // OutPt: vertex data structure for clipping solutions
- internal class OutPt
- {
- public Point64 pt;
- public OutPt? next;
- public OutPt prev;
- public OutRec outrec;
- public Joiner? joiner;
- public OutPt(Point64 pt, OutRec outrec)
- {
- this.pt = pt;
- this.outrec = outrec;
- next = this;
- prev = this;
- joiner = null;
- }
- };
- // OutRec: path data structure for clipping solutions
- internal class OutRec
- {
- public int idx;
- public OutRec? owner;
- public List<OutRec>? splits;
- public Active? frontEdge;
- public Active? backEdge;
- public OutPt? pts;
- public PolyPathNode? polypath;
- public Rect64 bounds;
- public Path64 path;
- public bool isOpen;
- public OutRec()
- {
- bounds = new Rect64();
- path = new Path64();
- }
- };
- // Joiner: structure used in merging "touching" solution polygons
- internal class Joiner
- {
- public int idx;
- public OutPt op1;
- public OutPt? op2;
- public Joiner? next1;
- public Joiner? next2;
- public Joiner? nextH;
- public Joiner(OutPt op1, OutPt? op2, Joiner? nextH)
- {
- idx = -1;
- this.nextH = nextH;
- this.op1 = op1;
- this.op2 = op2;
- next1 = op1.joiner;
- op1.joiner = this;
- if (op2 != null)
- {
- next2 = op2.joiner;
- op2.joiner = this;
- }
- else
- next2 = null;
- }
- }
- ///////////////////////////////////////////////////////////////////
- // Important: UP and DOWN here are premised on Y-axis positive down
- // displays, which is the orientation used in Clipper's development.
- ///////////////////////////////////////////////////////////////////
- internal class Active
- {
- public Point64 bot;
- public Point64 top;
- public long curX; // current (updated at every new scanline)
- public double dx;
- public int windDx; // 1 or -1 depending on winding direction
- public int windCount;
- public int windCount2; // winding count of the opposite polytype
- public OutRec? outrec;
- // AEL: 'active edge list' (Vatti's AET - active edge table)
- // a linked list of all edges (from left to right) that are present
- // (or 'active') within the current scanbeam (a horizontal 'beam' that
- // sweeps from bottom to top over the paths in the clipping operation).
- public Active? prevInAEL;
- public Active? nextInAEL;
- // SEL: 'sorted edge list' (Vatti's ST - sorted table)
- // linked list used when sorting edges into their new positions at the
- // top of scanbeams, but also (re)used to process horizontals.
- public Active? prevInSEL;
- public Active? nextInSEL;
- public Active? jump;
- public Vertex? vertexTop;
- public LocalMinima localMin; // the bottom of an edge 'bound' (also Vatti)
- internal bool isLeftBound;
- };
- public class ClipperBase
- {
- private ClipType _cliptype;
- private FillRule _fillrule;
- private Active? _actives;
- private Active? _sel;
- private Joiner? _horzJoiners;
- private readonly List<LocalMinima> _minimaList;
- private readonly List<IntersectNode> _intersectList;
- private readonly List<Vertex> _vertexList;
- private readonly List<OutRec> _outrecList;
- private readonly List<Joiner?> _joinerList;
- private readonly List<long> _scanlineList;
- private int _currentLocMin;
- private long _currentBotY;
- private bool _isSortedMinimaList;
- private bool _hasOpenPaths;
- internal bool _using_polytree;
- internal bool _succeeded;
- public bool PreserveCollinear { get; set; }
- public bool ReverseSolution { get; set; }
- #if USINGZ
- public delegate void ZCallback64(Point64 bot1, Point64 top1,
- Point64 bot2, Point64 top2, ref Point64 intersectPt);
- public long DefaultZ { get; set; }
- protected ZCallback64? _zCallback;
- #endif
- public ClipperBase()
- {
- _minimaList = new List<LocalMinima>();
- _intersectList = new List<IntersectNode>();
- _vertexList = new List<Vertex>();
- _outrecList = new List<OutRec>();
- _joinerList = new List<Joiner?>();
- _scanlineList = new List<long>();
- PreserveCollinear = true;
- }
- #if USINGZ
- private bool XYCoordsEqual(Point64 pt1, Point64 pt2)
- {
- return (pt1.X == pt2.X && pt1.Y == pt2.Y);
- }
-
- private void SetZ(Active e1, Active e2, ref Point64 intersectPt)
- {
- if (_zCallback == null) return;
- // prioritize subject vertices over clip vertices
- // and pass the subject vertices before clip vertices in the callback
- if (GetPolyType(e1) == PathType.Subject)
- {
- if (XYCoordsEqual(intersectPt, e1.bot))
- intersectPt = new Point64(intersectPt.X, intersectPt.Y, e1.bot.Z);
- else if (XYCoordsEqual(intersectPt, e1.top))
- intersectPt = new Point64(intersectPt.X, intersectPt.Y, e1.top.Z);
- else if (XYCoordsEqual(intersectPt, e2.bot))
- intersectPt = new Point64(intersectPt.X, intersectPt.Y, e2.bot.Z);
- else if (XYCoordsEqual(intersectPt, e2.top))
- intersectPt = new Point64(intersectPt.X, intersectPt.Y, e2.top.Z);
- else
- intersectPt = new Point64(intersectPt.X, intersectPt.Y, DefaultZ);
- _zCallback(e1.bot, e1.top, e2.bot, e2.top, ref intersectPt);
- }
- else
- {
- if (XYCoordsEqual(intersectPt, e2.bot))
- intersectPt = new Point64(intersectPt.X, intersectPt.Y, e2.bot.Z);
- else if (XYCoordsEqual(intersectPt, e2.top))
- intersectPt = new Point64(intersectPt.X, intersectPt.Y, e2.top.Z);
- else if (XYCoordsEqual(intersectPt, e1.bot))
- intersectPt = new Point64(intersectPt.X, intersectPt.Y, e1.bot.Z);
- else if (XYCoordsEqual(intersectPt, e1.top))
- intersectPt = new Point64(intersectPt.X, intersectPt.Y, e1.top.Z);
- else
- intersectPt = new Point64(intersectPt.X, intersectPt.Y, DefaultZ);
- _zCallback(e2.bot, e2.top, e1.bot, e1.top, ref intersectPt);
- }
- }
- #endif
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsOdd(int val)
- {
- return ((val & 1) != 0);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsHotEdge(Active ae)
- {
- return ae.outrec != null;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsOpen(Active ae)
- {
- return ae.localMin.isOpen;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsOpenEnd(Active ae)
- {
- return ae.localMin.isOpen && IsOpenEnd(ae.vertexTop!);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsOpenEnd(Vertex v)
- {
- return (v.flags & (VertexFlags.OpenStart | VertexFlags.OpenEnd)) != VertexFlags.None;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Active? GetPrevHotEdge(Active ae)
- {
- Active? prev = ae.prevInAEL;
- while (prev != null && (IsOpen(prev) || !IsHotEdge(prev)))
- prev = prev.prevInAEL;
- return prev;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsFront(Active ae)
- {
- return (ae == ae.outrec!.frontEdge);
- }
- /*******************************************************************************
- * Dx: 0(90deg) *
- * | *
- * +inf (180deg) <--- o --. -inf (0deg) *
- *******************************************************************************/
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static double GetDx(Point64 pt1, Point64 pt2)
- {
- double dy = pt2.Y - pt1.Y;
- if (dy != 0)
- return (pt2.X - pt1.X) / dy;
- if (pt2.X > pt1.X)
- return double.NegativeInfinity;
- return double.PositiveInfinity;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static long TopX(Active ae, long currentY)
- {
- if ((currentY == ae.top.Y) || (ae.top.X == ae.bot.X)) return ae.top.X;
- if (currentY == ae.bot.Y) return ae.bot.X;
- return ae.bot.X + (long)Math.Round(ae.dx * (currentY - ae.bot.Y));
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsHorizontal(Active ae)
- {
- return (ae.top.Y == ae.bot.Y);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsHeadingRightHorz(Active ae)
- {
- return (double.IsNegativeInfinity(ae.dx));
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsHeadingLeftHorz(Active ae)
- {
- return (double.IsPositiveInfinity(ae.dx));
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void SwapActives(ref Active ae1, ref Active ae2)
- {
- (ae2, ae1) = (ae1, ae2);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static PathType GetPolyType(Active ae)
- {
- return ae.localMin.polytype;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsSamePolyType(Active ae1, Active ae2)
- {
- return ae1.localMin.polytype == ae2.localMin.polytype;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void SetDx(Active ae)
- {
- ae.dx = GetDx(ae.bot, ae.top);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Vertex NextVertex(Active ae)
- {
- if (ae.windDx > 0)
- return ae.vertexTop!.next!;
- return ae.vertexTop!.prev!;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Vertex PrevPrevVertex(Active ae)
- {
- if (ae.windDx > 0)
- return ae.vertexTop!.prev!.prev!;
- return ae.vertexTop!.next!.next!;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsMaxima(Vertex vertex)
- {
- return ((vertex.flags & VertexFlags.LocalMax) != VertexFlags.None);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsMaxima(Active ae)
- {
- return IsMaxima(ae.vertexTop!);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Active? GetMaximaPair(Active ae)
- {
- Active? ae2;
- ae2 = ae.nextInAEL;
- while (ae2 != null)
- {
- if (ae2.vertexTop == ae.vertexTop) return ae2; // Found!
- ae2 = ae2.nextInAEL;
- }
- return null;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Vertex? GetCurrYMaximaVertex(Active ae)
- {
- Vertex? result = ae.vertexTop;
- if (ae.windDx > 0)
- while (result!.next!.pt.Y == result.pt.Y) result = result.next;
- else
- while (result!.prev!.pt.Y == result.pt.Y) result = result.prev;
- if (!IsMaxima(result)) result = null; // not a maxima
- return result;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Active? GetHorzMaximaPair(Active horz, Vertex maxVert)
- {
- // we can't be sure whether the MaximaPair is on the left or right, so ...
- Active? result = horz.prevInAEL;
- while (result != null && result.curX >= maxVert.pt.X)
- {
- if (result.vertexTop == maxVert) return result; // Found!
- result = result.prevInAEL;
- }
- result = horz.nextInAEL;
- while (result != null && TopX(result, horz.top.Y) <= maxVert.pt.X)
- {
- if (result.vertexTop == maxVert) return result; // Found!
- result = result.nextInAEL;
- }
- return null;
- }
- private struct IntersectListSort : IComparer<IntersectNode>
- {
- public int Compare(IntersectNode a, IntersectNode b)
- {
- if (a.pt.Y == b.pt.Y)
- {
- if (a.pt.X == b.pt.X) return 0;
- return (a.pt.X < b.pt.X) ? -1 : 1;
- }
- return (a.pt.Y > b.pt.Y) ? -1 : 1;
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void SetSides(OutRec outrec, Active startEdge, Active endEdge)
- {
- outrec.frontEdge = startEdge;
- outrec.backEdge = endEdge;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void SwapOutrecs(Active ae1, Active ae2)
- {
- OutRec? or1 = ae1.outrec; // at least one edge has
- OutRec? or2 = ae2.outrec; // an assigned outrec
- if (or1 == or2)
- {
- Active? ae = or1!.frontEdge;
- or1.frontEdge = or1.backEdge;
- or1.backEdge = ae;
- return;
- }
- if (or1 != null)
- {
- if (ae1 == or1.frontEdge)
- or1.frontEdge = ae2;
- else
- or1.backEdge = ae2;
- }
- if (or2 != null)
- {
- if (ae2 == or2.frontEdge)
- or2.frontEdge = ae1;
- else
- or2.backEdge = ae1;
- }
- ae1.outrec = or2;
- ae2.outrec = or1;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static double Area(OutPt op)
- {
- // https://en.wikipedia.org/wiki/Shoelace_formula
- double area = 0.0;
- OutPt op2 = op;
- do
- {
- area += (double)(op2.prev.pt.Y + op2.pt.Y) *
- (op2.prev.pt.X - op2.pt.X);
- op2 = op2.next!;
- } while (op2 != op);
- return area * 0.5;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static double AreaTriangle(Point64 pt1, Point64 pt2, Point64 pt3)
- {
- return (double)(pt3.Y + pt1.Y) * (pt3.X - pt1.X) +
- (double)(pt1.Y + pt2.Y) * (pt1.X - pt2.X) +
- (double)(pt2.Y + pt3.Y) * (pt2.X - pt3.X);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static OutRec? GetRealOutRec(OutRec? outRec)
- {
- while ((outRec != null) && (outRec.pts == null))
- outRec = outRec.owner;
- return outRec;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void UncoupleOutRec(Active ae)
- {
- OutRec? outrec = ae.outrec;
- if (outrec == null) return;
- outrec.frontEdge!.outrec = null;
- outrec.backEdge!.outrec = null;
- outrec.frontEdge = null;
- outrec.backEdge = null;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool OutrecIsAscending(Active hotEdge)
- {
- return (hotEdge == hotEdge.outrec!.frontEdge);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void SwapFrontBackSides(OutRec outrec)
- {
- // while this proc. is needed for open paths
- // it's almost never needed for closed paths
- Active ae2 = outrec.frontEdge!;
- outrec.frontEdge = outrec.backEdge;
- outrec.backEdge = ae2;
- outrec.pts = outrec.pts!.next;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool EdgesAdjacentInAEL(IntersectNode inode)
- {
- return (inode.edge1.nextInAEL == inode.edge2) || (inode.edge1.prevInAEL == inode.edge2);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected void ClearSolution()
- {
- while (_actives != null) DeleteFromAEL(_actives);
- _scanlineList.Clear();
- DisposeIntersectNodes();
- _joinerList.Clear();
- _horzJoiners = null;
- _outrecList.Clear();
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Clear()
- {
- ClearSolution();
- _minimaList.Clear();
- _vertexList.Clear();
- _currentLocMin = 0;
- _isSortedMinimaList = false;
- _hasOpenPaths = false;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected void Reset()
- {
- if (!_isSortedMinimaList)
- {
- _minimaList.Sort(new LocMinSorter());
- _isSortedMinimaList = true;
- }
- _scanlineList.Capacity = _minimaList.Count;
- for (int i = _minimaList.Count - 1; i >= 0; i--)
- _scanlineList.Add(_minimaList[i].vertex.pt.Y);
- _currentBotY = 0;
- _currentLocMin = 0;
- _actives = null;
- _sel = null;
- _succeeded = true;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void InsertScanline(long y)
- {
- int index = _scanlineList.BinarySearch(y);
- if (index >= 0) return;
- index = ~index;
- _scanlineList.Insert(index, y);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool PopScanline(out long y)
- {
- int cnt = _scanlineList.Count - 1;
- if (cnt < 0)
- {
- y = 0;
- return false;
- }
- y = _scanlineList[cnt];
- _scanlineList.RemoveAt(cnt--);
- while (cnt >= 0 && y == _scanlineList[cnt])
- _scanlineList.RemoveAt(cnt--);
- return true;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool HasLocMinAtY(long y)
- {
- return (_currentLocMin < _minimaList.Count && _minimaList[_currentLocMin].vertex.pt.Y == y);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private LocalMinima PopLocalMinima()
- {
- return _minimaList[_currentLocMin++];
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void AddLocMin(Vertex vert, PathType polytype, bool isOpen)
- {
- // make sure the vertex is added only once ...
- if ((vert.flags & VertexFlags.LocalMin) != VertexFlags.None) return;
- vert.flags |= VertexFlags.LocalMin;
- LocalMinima lm = new LocalMinima(vert, polytype, isOpen);
- _minimaList.Add(lm);
- }
- protected void AddPathsToVertexList(Paths64 paths, PathType polytype, bool isOpen)
- {
- int totalVertCnt = 0;
- foreach (Path64 path in paths) totalVertCnt += path.Count;
- _vertexList.Capacity = _vertexList.Count + totalVertCnt;
- foreach (Path64 path in paths)
- {
- Vertex? v0 = null, prev_v = null, curr_v;
- foreach (Point64 pt in path)
- {
- if (v0 == null)
- {
- v0 = new Vertex(pt, VertexFlags.None, null);
- _vertexList.Add(v0);
- prev_v = v0;
- }
- else if (prev_v!.pt != pt) // ie skips duplicates
- {
- curr_v = new Vertex(pt, VertexFlags.None, prev_v);
- _vertexList.Add(curr_v);
- prev_v.next = curr_v;
- prev_v = curr_v;
- }
- }
- if (prev_v == null || prev_v.prev == null) continue;
- if (!isOpen && prev_v.pt == v0!.pt) prev_v = prev_v.prev;
- prev_v.next = v0;
- v0!.prev = prev_v;
- if (!isOpen && prev_v.next == prev_v) continue;
- // OK, we have a valid path
- bool going_up, going_up0;
- if (isOpen)
- {
- curr_v = v0.next;
- while (curr_v != v0 && curr_v!.pt.Y == v0.pt.Y)
- curr_v = curr_v.next;
- going_up = curr_v.pt.Y <= v0.pt.Y;
- if (going_up)
- {
- v0.flags = VertexFlags.OpenStart;
- AddLocMin(v0, polytype, true);
- }
- else
- v0.flags = VertexFlags.OpenStart | VertexFlags.LocalMax;
- }
- else // closed path
- {
- prev_v = v0.prev;
- while (prev_v != v0 && prev_v!.pt.Y == v0.pt.Y)
- prev_v = prev_v.prev;
- if (prev_v == v0)
- continue; // only open paths can be completely flat
- going_up = prev_v.pt.Y > v0.pt.Y;
- }
- going_up0 = going_up;
- prev_v = v0;
- curr_v = v0.next;
- while (curr_v != v0)
- {
- if (curr_v!.pt.Y > prev_v.pt.Y && going_up)
- {
- prev_v.flags |= VertexFlags.LocalMax;
- going_up = false;
- }
- else if (curr_v.pt.Y < prev_v.pt.Y && !going_up)
- {
- going_up = true;
- AddLocMin(prev_v, polytype, isOpen);
- }
- prev_v = curr_v;
- curr_v = curr_v.next;
- }
- if (isOpen)
- {
- prev_v.flags |= VertexFlags.OpenEnd;
- if (going_up)
- prev_v.flags |= VertexFlags.LocalMax;
- else
- AddLocMin(prev_v, polytype, isOpen);
- }
- else if (going_up != going_up0)
- {
- if (going_up0) AddLocMin(prev_v, polytype, false);
- else prev_v.flags |= VertexFlags.LocalMax;
- }
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddSubject(Path64 path)
- {
- AddPath(path, PathType.Subject);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddOpenSubject(Path64 path)
- {
- AddPath(path, PathType.Subject, true);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddClip(Path64 path)
- {
- AddPath(path, PathType.Clip);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected void AddPath(Path64 path, PathType polytype, bool isOpen = false)
- {
- Paths64 tmp = new Paths64(1) { path };
- AddPaths(tmp, polytype, isOpen);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected void AddPaths(Paths64 paths, PathType polytype, bool isOpen = false)
- {
- if (isOpen) _hasOpenPaths = true;
- _isSortedMinimaList = false;
- AddPathsToVertexList(paths, polytype, isOpen);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool IsContributingClosed(Active ae)
- {
- switch (_fillrule)
- {
- case FillRule.Positive:
- if (ae.windCount != 1) return false;
- break;
- case FillRule.Negative:
- if (ae.windCount != -1) return false;
- break;
- case FillRule.NonZero:
- if (Math.Abs(ae.windCount) != 1) return false;
- break;
- }
- switch (_cliptype)
- {
- case ClipType.Intersection:
- return _fillrule switch
- {
- FillRule.Positive => ae.windCount2 > 0,
- FillRule.Negative => ae.windCount2 < 0,
- _ => ae.windCount2 != 0,
- };
- case ClipType.Union:
- return _fillrule switch
- {
- FillRule.Positive => ae.windCount2 <= 0,
- FillRule.Negative => ae.windCount2 >= 0,
- _ => ae.windCount2 == 0,
- };
- case ClipType.Difference:
- bool result = _fillrule switch
- {
- FillRule.Positive => (ae.windCount2 <= 0),
- FillRule.Negative => (ae.windCount2 >= 0),
- _ => (ae.windCount2 == 0),
- };
- return (GetPolyType(ae) == PathType.Subject) ? result : !result;
- case ClipType.Xor:
- return true; // XOr is always contributing unless open
- default:
- return false;
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool IsContributingOpen(Active ae)
- {
- bool isInClip, isInSubj;
- switch (_fillrule)
- {
- case FillRule.Positive:
- isInSubj = ae.windCount > 0;
- isInClip = ae.windCount2 > 0;
- break;
- case FillRule.Negative:
- isInSubj = ae.windCount < 0;
- isInClip = ae.windCount2 < 0;
- break;
- default:
- isInSubj = ae.windCount != 0;
- isInClip = ae.windCount2 != 0;
- break;
- }
- bool result = _cliptype switch
- {
- ClipType.Intersection => isInClip,
- ClipType.Union => !isInSubj && !isInClip,
- _ => !isInClip
- };
- return result;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void SetWindCountForClosedPathEdge(Active ae)
- {
- // Wind counts refer to polygon regions not edges, so here an edge's WindCnt
- // indicates the higher of the wind counts for the two regions touching the
- // edge. (nb: Adjacent regions can only ever have their wind counts differ by
- // one. Also, open paths have no meaningful wind directions or counts.)
- Active? ae2 = ae.prevInAEL;
- // find the nearest closed path edge of the same PolyType in AEL (heading left)
- PathType pt = GetPolyType(ae);
- while (ae2 != null && (GetPolyType(ae2) != pt || IsOpen(ae2))) ae2 = ae2.prevInAEL;
- if (ae2 == null)
- {
- ae.windCount = ae.windDx;
- ae2 = _actives;
- }
- else if (_fillrule == FillRule.EvenOdd)
- {
- ae.windCount = ae.windDx;
- ae.windCount2 = ae2.windCount2;
- ae2 = ae2.nextInAEL;
- }
- else
- {
- // NonZero, positive, or negative filling here ...
- // when e2's WindCnt is in the SAME direction as its WindDx,
- // then polygon will fill on the right of 'e2' (and 'e' will be inside)
- // nb: neither e2.WindCnt nor e2.WindDx should ever be 0.
- if (ae2.windCount * ae2.windDx < 0)
- {
- // opposite directions so 'ae' is outside 'ae2' ...
- if (Math.Abs(ae2.windCount) > 1)
- {
- // outside prev poly but still inside another.
- if (ae2.windDx * ae.windDx < 0)
- // reversing direction so use the same WC
- ae.windCount = ae2.windCount;
- else
- // otherwise keep 'reducing' the WC by 1 (i.e. towards 0) ...
- ae.windCount = ae2.windCount + ae.windDx;
- }
- else
- // now outside all polys of same polytype so set own WC ...
- ae.windCount = (IsOpen(ae) ? 1 : ae.windDx);
- }
- else
- {
- //'ae' must be inside 'ae2'
- if (ae2.windDx * ae.windDx < 0)
- // reversing direction so use the same WC
- ae.windCount = ae2.windCount;
- else
- // otherwise keep 'increasing' the WC by 1 (i.e. away from 0) ...
- ae.windCount = ae2.windCount + ae.windDx;
- }
- ae.windCount2 = ae2.windCount2;
- ae2 = ae2.nextInAEL; // i.e. get ready to calc WindCnt2
- }
- // update windCount2 ...
- if (_fillrule == FillRule.EvenOdd)
- while (ae2 != ae)
- {
- if (GetPolyType(ae2!) != pt && !IsOpen(ae2!))
- ae.windCount2 = (ae.windCount2 == 0 ? 1 : 0);
- ae2 = ae2!.nextInAEL;
- }
- else
- while (ae2 != ae)
- {
- if (GetPolyType(ae2!) != pt && !IsOpen(ae2!))
- ae.windCount2 += ae2!.windDx;
- ae2 = ae2!.nextInAEL;
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void SetWindCountForOpenPathEdge(Active ae)
- {
- Active? ae2 = _actives;
- if (_fillrule == FillRule.EvenOdd)
- {
- int cnt1 = 0, cnt2 = 0;
- while (ae2 != ae)
- {
- if (GetPolyType(ae2!) == PathType.Clip)
- cnt2++;
- else if (!IsOpen(ae2!))
- cnt1++;
- ae2 = ae2!.nextInAEL;
- }
- ae.windCount = (IsOdd(cnt1) ? 1 : 0);
- ae.windCount2 = (IsOdd(cnt2) ? 1 : 0);
- }
- else
- {
- while (ae2 != ae)
- {
- if (GetPolyType(ae2!) == PathType.Clip)
- ae.windCount2 += ae2!.windDx;
- else if (!IsOpen(ae2!))
- ae.windCount += ae2!.windDx;
- ae2 = ae2!.nextInAEL;
- }
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsValidAelOrder(Active resident, Active newcomer)
- {
- if (newcomer.curX != resident.curX)
- return newcomer.curX > resident.curX;
- // get the turning direction a1.top, a2.bot, a2.top
- double d = InternalClipper.CrossProduct(resident.top, newcomer.bot, newcomer.top);
- if (d != 0) return (d < 0);
- // edges must be collinear to get here
- // for starting open paths, place them according to
- // the direction they're about to turn
- if (!IsMaxima(resident) && (resident.top.Y > newcomer.top.Y))
- {
- return InternalClipper.CrossProduct(newcomer.bot,
- resident.top, NextVertex(resident).pt) <= 0;
- }
- if (!IsMaxima(newcomer) && (newcomer.top.Y > resident.top.Y))
- {
- return InternalClipper.CrossProduct(newcomer.bot,
- newcomer.top, NextVertex(newcomer).pt) >= 0;
- }
- long y = newcomer.bot.Y;
- bool newcomerIsLeft = newcomer.isLeftBound;
- if (resident.bot.Y != y || resident.localMin.vertex.pt.Y != y)
- return newcomer.isLeftBound;
- // resident must also have just been inserted
- if (resident.isLeftBound != newcomerIsLeft)
- return newcomerIsLeft;
- if (InternalClipper.CrossProduct(PrevPrevVertex(resident).pt,
- resident.bot, resident.top) == 0) return true;
- // compare turning direction of the alternate bound
- return (InternalClipper.CrossProduct(PrevPrevVertex(resident).pt,
- newcomer.bot, PrevPrevVertex(newcomer).pt) > 0) == newcomerIsLeft;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void InsertLeftEdge(Active ae)
- {
- Active ae2;
- if (_actives == null)
- {
- ae.prevInAEL = null;
- ae.nextInAEL = null;
- _actives = ae;
- }
- else if (!IsValidAelOrder(_actives, ae))
- {
- ae.prevInAEL = null;
- ae.nextInAEL = _actives;
- _actives.prevInAEL = ae;
- _actives = ae;
- }
- else
- {
- ae2 = _actives;
- while (ae2.nextInAEL != null && IsValidAelOrder(ae2.nextInAEL, ae))
- ae2 = ae2.nextInAEL;
- ae.nextInAEL = ae2.nextInAEL;
- if (ae2.nextInAEL != null) ae2.nextInAEL.prevInAEL = ae;
- ae.prevInAEL = ae2;
- ae2.nextInAEL = ae;
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void InsertRightEdge(Active ae, Active ae2)
- {
- ae2.nextInAEL = ae.nextInAEL;
- if (ae.nextInAEL != null) ae.nextInAEL.prevInAEL = ae2;
- ae2.prevInAEL = ae;
- ae.nextInAEL = ae2;
- }
- private void InsertLocalMinimaIntoAEL(long botY)
- {
- LocalMinima localMinima;
- Active? leftBound, rightBound;
- // Add any local minima (if any) at BotY ...
- // NB horizontal local minima edges should contain locMin.vertex.prev
- while (HasLocMinAtY(botY))
- {
- localMinima = PopLocalMinima();
- if ((localMinima.vertex.flags & VertexFlags.OpenStart) != VertexFlags.None)
- {
- leftBound = null;
- }
- else
- {
- leftBound = new Active
- {
- bot = localMinima.vertex.pt,
- curX = localMinima.vertex.pt.X,
- windDx = -1,
- vertexTop = localMinima.vertex.prev,
- top = localMinima.vertex.prev!.pt,
- outrec = null,
- localMin = localMinima
- };
- SetDx(leftBound);
- }
- if ((localMinima.vertex.flags & VertexFlags.OpenEnd) != VertexFlags.None)
- {
- rightBound = null;
- }
- else
- {
- rightBound = new Active
- {
- bot = localMinima.vertex.pt,
- curX = localMinima.vertex.pt.X,
- windDx = 1,
- vertexTop = localMinima.vertex.next, // i.e. ascending
- top = localMinima.vertex.next!.pt,
- outrec = null,
- localMin = localMinima
- };
- SetDx(rightBound);
- }
- // Currently LeftB is just the descending bound and RightB is the ascending.
- // Now if the LeftB isn't on the left of RightB then we need swap them.
- if (leftBound != null && rightBound != null)
- {
- if (IsHorizontal(leftBound))
- {
- if (IsHeadingRightHorz(leftBound)) SwapActives(ref leftBound, ref rightBound);
- }
- else if (IsHorizontal(rightBound))
- {
- if (IsHeadingLeftHorz(rightBound)) SwapActives(ref leftBound, ref rightBound);
- }
- else if (leftBound.dx < rightBound.dx)
- SwapActives(ref leftBound, ref rightBound);
- //so when leftBound has windDx == 1, the polygon will be oriented
- //counter-clockwise in Cartesian coords (clockwise with inverted Y).
- }
- else if (leftBound == null)
- {
- leftBound = rightBound;
- rightBound = null;
- }
- bool contributing;
- leftBound!.isLeftBound = true;
- InsertLeftEdge(leftBound);
- if (IsOpen(leftBound))
- {
- SetWindCountForOpenPathEdge(leftBound);
- contributing = IsContributingOpen(leftBound);
- }
- else
- {
- SetWindCountForClosedPathEdge(leftBound);
- contributing = IsContributingClosed(leftBound);
- }
- if (rightBound != null)
- {
- rightBound.windCount = leftBound.windCount;
- rightBound.windCount2 = leftBound.windCount2;
- InsertRightEdge(leftBound, rightBound); ///////
- if (contributing)
- {
- AddLocalMinPoly(leftBound, rightBound, leftBound.bot, true);
- if (!IsHorizontal(leftBound) && TestJoinWithPrev1(leftBound))
- {
- OutPt op = AddOutPt(leftBound.prevInAEL!, leftBound.bot);
- AddJoin(op, leftBound.outrec!.pts!);
- }
- }
- while (rightBound.nextInAEL != null &&
- IsValidAelOrder(rightBound.nextInAEL, rightBound))
- {
- IntersectEdges(rightBound, rightBound.nextInAEL, rightBound.bot);
- SwapPositionsInAEL(rightBound, rightBound.nextInAEL);
- }
- if (!IsHorizontal(rightBound) && TestJoinWithNext1(rightBound))
- {
- OutPt op = AddOutPt(rightBound.nextInAEL!, rightBound.bot);
- AddJoin(rightBound.outrec!.pts!, op);
- }
- if (IsHorizontal(rightBound))
- PushHorz(rightBound);
- else
- InsertScanline(rightBound.top.Y);
- }
- else if (contributing)
- StartOpenPath(leftBound, leftBound.bot);
- if (IsHorizontal(leftBound))
- PushHorz(leftBound);
- else
- InsertScanline(leftBound.top.Y);
- } // while (HasLocMinAtY())
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void PushHorz(Active ae)
- {
- ae.nextInSEL = _sel;
- _sel = ae;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool PopHorz(out Active? ae)
- {
- ae = _sel;
- if (_sel == null) return false;
- _sel = _sel.nextInSEL;
- return true;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool TestJoinWithPrev1(Active e)
- {
- // this is marginally quicker than TestJoinWithPrev2
- // but can only be used when e.PrevInAEL.currX is accurate
- return IsHotEdge(e) && !IsOpen(e) &&
- (e.prevInAEL != null) && (e.prevInAEL.curX == e.curX) &&
- IsHotEdge(e.prevInAEL) && !IsOpen(e.prevInAEL) &&
- (InternalClipper.CrossProduct(e.prevInAEL.top, e.bot, e.top) == 0);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool TestJoinWithPrev2(Active e, Point64 currPt)
- {
- return IsHotEdge(e) && !IsOpen(e) &&
- (e.prevInAEL != null) && !IsOpen(e.prevInAEL) &&
- IsHotEdge(e.prevInAEL) && (e.prevInAEL.top.Y < e.bot.Y) &&
- (Math.Abs(TopX(e.prevInAEL, currPt.Y) - currPt.X) < 2) &&
- (InternalClipper.CrossProduct(e.prevInAEL.top, currPt, e.top) == 0);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool TestJoinWithNext1(Active e)
- {
- // this is marginally quicker than TestJoinWithNext2
- // but can only be used when e.NextInAEL.currX is accurate
- return IsHotEdge(e) && !IsOpen(e) &&
- (e.nextInAEL != null) && (e.nextInAEL.curX == e.curX) &&
- IsHotEdge(e.nextInAEL) && !IsOpen(e.nextInAEL) &&
- (InternalClipper.CrossProduct(e.nextInAEL.top, e.bot, e.top) == 0);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool TestJoinWithNext2(Active e, Point64 currPt)
- {
- return IsHotEdge(e) && !IsOpen(e) &&
- (e.nextInAEL != null) && !IsOpen(e.nextInAEL) &&
- IsHotEdge(e.nextInAEL) && (e.nextInAEL.top.Y < e.bot.Y) &&
- (Math.Abs(TopX(e.nextInAEL, currPt.Y) - currPt.X) < 2) &&
- (InternalClipper.CrossProduct(e.nextInAEL.top, currPt, e.top) == 0);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private OutPt AddLocalMinPoly(Active ae1, Active ae2, Point64 pt, bool isNew = false)
- {
- OutRec outrec = new OutRec();
- _outrecList.Add(outrec);
- outrec.idx = _outrecList.Count - 1;
- outrec.pts = null;
- outrec.polypath = null;
- ae1.outrec = outrec;
- ae2.outrec = outrec;
- // Setting the owner and inner/outer states (above) is an essential
- // precursor to setting edge 'sides' (ie left and right sides of output
- // polygons) and hence the orientation of output paths ...
- if (IsOpen(ae1))
- {
- outrec.owner = null;
- outrec.isOpen = true;
- if (ae1.windDx > 0)
- SetSides(outrec, ae1, ae2);
- else
- SetSides(outrec, ae2, ae1);
- }
- else
- {
- outrec.isOpen = false;
- Active? prevHotEdge = GetPrevHotEdge(ae1);
- // e.windDx is the winding direction of the **input** paths
- // and unrelated to the winding direction of output polygons.
- // Output orientation is determined by e.outrec.frontE which is
- // the ascending edge (see AddLocalMinPoly).
- if (prevHotEdge != null)
- {
- outrec.owner = prevHotEdge.outrec;
- if (OutrecIsAscending(prevHotEdge) == isNew)
- SetSides(outrec, ae2, ae1);
- else
- SetSides(outrec, ae1, ae2);
- }
- else
- {
- outrec.owner = null;
- if (isNew)
- SetSides(outrec, ae1, ae2);
- else
- SetSides(outrec, ae2, ae1);
- }
- }
- OutPt op = new OutPt(pt, outrec);
- outrec.pts = op;
- return op;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private OutPt? AddLocalMaxPoly(Active ae1, Active ae2, Point64 pt)
- {
- if (IsFront(ae1) == IsFront(ae2))
- {
- if (IsOpenEnd(ae1))
- SwapFrontBackSides(ae1.outrec!);
- else if (IsOpenEnd(ae2))
- SwapFrontBackSides(ae2.outrec!);
- else
- {
- _succeeded = false;
- return null;
- }
- }
- OutPt result = AddOutPt(ae1, pt);
- if (ae1.outrec == ae2.outrec)
- {
- OutRec outrec = ae1.outrec!;
- outrec.pts = result;
- UncoupleOutRec(ae1);
- if (!IsOpen(ae1))
- CleanCollinear(outrec);
- result = outrec.pts;
- outrec.owner = GetRealOutRec(outrec.owner);
- if (_using_polytree && outrec.owner is { frontEdge: null })
- outrec.owner = GetRealOutRec(outrec.owner.owner);
- }
- // and to preserve the winding orientation of outrec ...
- else if (IsOpen(ae1))
- {
- if (ae1.windDx < 0)
- JoinOutrecPaths(ae1, ae2);
- else
- JoinOutrecPaths(ae2, ae1);
- }
- else if (ae1.outrec!.idx < ae2.outrec!.idx)
- JoinOutrecPaths(ae1, ae2);
- else
- JoinOutrecPaths(ae2, ae1);
- return result;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void JoinOutrecPaths(Active ae1, Active ae2)
- {
- // join ae2 outrec path onto ae1 outrec path and then delete ae2 outrec path
- // pointers. (NB Only very rarely do the joining ends share the same coords.)
- OutPt p1Start = ae1.outrec!.pts!;
- OutPt p2Start = ae2.outrec!.pts!;
- OutPt p1End = p1Start.next!;
- OutPt p2End = p2Start.next!;
- if (IsFront(ae1))
- {
- p2End.prev = p1Start;
- p1Start.next = p2End;
- p2Start.next = p1End;
- p1End.prev = p2Start;
- ae1.outrec.pts = p2Start;
- // nb: if IsOpen(e1) then e1 & e2 must be a 'maximaPair'
- ae1.outrec.frontEdge = ae2.outrec.frontEdge;
- if (ae1.outrec.frontEdge != null)
- ae1.outrec.frontEdge!.outrec = ae1.outrec;
- }
- else
- {
- p1End.prev = p2Start;
- p2Start.next = p1End;
- p1Start.next = p2End;
- p2End.prev = p1Start;
- ae1.outrec.backEdge = ae2.outrec.backEdge;
- if (ae1.outrec.backEdge != null)
- ae1.outrec.backEdge!.outrec = ae1.outrec;
- }
- // an owner must have a lower idx otherwise
- // it won't be a valid owner
- if (ae2.outrec.owner != null &&
- ae2.outrec.owner.idx < ae1.outrec.idx)
- {
- if (ae1.outrec.owner == null || ae2.outrec.owner.idx < ae1.outrec.owner.idx)
- ae1.outrec.owner = ae2.outrec.owner;
- }
- // after joining, the ae2.OutRec must contains no vertices ...
- ae2.outrec.frontEdge = null;
- ae2.outrec.backEdge = null;
- ae2.outrec.pts = null;
- ae2.outrec.owner = ae1.outrec; // this may be redundant
- if (IsOpenEnd(ae1))
- {
- ae2.outrec.pts = ae1.outrec.pts;
- ae1.outrec.pts = null;
- }
- // and ae1 and ae2 are maxima and are about to be dropped from the Actives list.
- ae1.outrec = null;
- ae2.outrec = null;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static OutPt AddOutPt(Active ae, Point64 pt)
- {
- OutPt newOp;
- // Outrec.OutPts: a circular doubly-linked-list of POutPt where ...
- // opFront[.Prev]* ~~~> opBack & opBack == opFront.Next
- OutRec outrec = ae.outrec!;
- bool toFront = IsFront(ae);
- OutPt opFront = outrec.pts!;
- OutPt opBack = opFront.next!;
- if (toFront && (pt == opFront.pt)) newOp = opFront;
- else if (!toFront && (pt == opBack.pt)) newOp = opBack;
- else
- {
- newOp = new OutPt(pt, outrec);
- opBack.prev = newOp;
- newOp.prev = opFront;
- newOp.next = opBack;
- opFront.next = newOp;
- if (toFront) outrec.pts = newOp;
- }
- return newOp;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private OutPt StartOpenPath(Active ae, Point64 pt)
- {
- OutRec outrec = new OutRec();
- _outrecList.Add(outrec);
- outrec.idx = _outrecList.Count - 1;
- outrec.owner = null;
- outrec.isOpen = true;
- outrec.pts = null;
- outrec.polypath = null;
- if (ae.windDx > 0)
- {
- outrec.frontEdge = ae;
- outrec.backEdge = null;
- }
- else
- {
- outrec.frontEdge = null;
- outrec.backEdge = ae;
- }
- ae.outrec = outrec;
- OutPt op = new OutPt(pt, outrec);
- outrec.pts = op;
- return op;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void UpdateEdgeIntoAEL(Active ae)
- {
- ae.bot = ae.top;
- ae.vertexTop = NextVertex(ae);
- ae.top = ae.vertexTop!.pt;
- ae.curX = ae.bot.X;
- SetDx(ae);
- if (IsHorizontal(ae)) return;
- InsertScanline(ae.top.Y);
- if (TestJoinWithPrev1(ae))
- {
- OutPt op1 = AddOutPt(ae.prevInAEL!, ae.bot);
- OutPt op2 = AddOutPt(ae, ae.bot);
- AddJoin(op1, op2);
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Active? FindEdgeWithMatchingLocMin(Active e)
- {
- Active? result = e.nextInAEL;
- while (result != null)
- {
- if (result.localMin == e.localMin) return result;
- if (!IsHorizontal(result) && e.bot != result.bot) result = null;
- else result = result.nextInAEL;
- }
- result = e.prevInAEL;
- while (result != null)
- {
- if (result.localMin == e.localMin) return result;
- if (!IsHorizontal(result) && e.bot != result.bot) return null;
- result = result.prevInAEL;
- }
- return result;
- }
- private OutPt? IntersectEdges(Active ae1, Active ae2, Point64 pt)
- {
- OutPt? resultOp = null;
- // MANAGE OPEN PATH INTERSECTIONS SEPARATELY ...
- if (_hasOpenPaths && (IsOpen(ae1) || IsOpen(ae2)))
- {
- if (IsOpen(ae1) && IsOpen(ae2)) return null;
- // the following line avoids duplicating quite a bit of code
- if (IsOpen(ae2)) SwapActives(ref ae1, ref ae2);
- if (_cliptype == ClipType.Union)
- {
- if (!IsHotEdge(ae2)) return null;
- }
- else if (ae2.localMin.polytype == PathType.Subject)
- return null;
- switch (_fillrule)
- {
- case FillRule.Positive:
- if (ae2.windCount != 1) return null; break;
- case FillRule.Negative:
- if (ae2.windCount != -1) return null; break;
- default:
- if (Math.Abs(ae2.windCount) != 1) return null; break;
- }
- // toggle contribution ...
- if (IsHotEdge(ae1))
- {
- resultOp = AddOutPt(ae1, pt);
- #if USINGZ
- SetZ(ae1, ae2, ref resultOp.pt);
- #endif
- if (IsFront(ae1))
- ae1.outrec!.frontEdge = null;
- else
- ae1.outrec!.backEdge = null;
- ae1.outrec = null;
- }
- // horizontal edges can pass under open paths at a LocMins
- else if (pt == ae1.localMin.vertex.pt &&
- !IsOpenEnd(ae1.localMin.vertex))
- {
- // find the other side of the LocMin and
- // if it's 'hot' join up with it ...
- Active? ae3 = FindEdgeWithMatchingLocMin(ae1);
- if (ae3 != null && IsHotEdge(ae3))
- {
- ae1.outrec = ae3.outrec;
- if (ae1.windDx > 0)
- SetSides(ae3.outrec!, ae1, ae3);
- else
- SetSides(ae3.outrec!, ae3, ae1);
- return ae3.outrec!.pts;
- }
- resultOp = StartOpenPath(ae1, pt);
- }
- else
- resultOp = StartOpenPath(ae1, pt);
- #if USINGZ
- SetZ(ae1, ae2, ref resultOp.pt);
- #endif
- return resultOp;
- }
- // MANAGING CLOSED PATHS FROM HERE ON
- // UPDATE WINDING COUNTS...
- int oldE1WindCount, oldE2WindCount;
- if (ae1.localMin.polytype == ae2.localMin.polytype)
- {
- if (_fillrule == FillRule.EvenOdd)
- {
- oldE1WindCount = ae1.windCount;
- ae1.windCount = ae2.windCount;
- ae2.windCount = oldE1WindCount;
- }
- else
- {
- if (ae1.windCount + ae2.windDx == 0)
- ae1.windCount = -ae1.windCount;
- else
- ae1.windCount += ae2.windDx;
- if (ae2.windCount - ae1.windDx == 0)
- ae2.windCount = -ae2.windCount;
- else
- ae2.windCount -= ae1.windDx;
- }
- }
- else
- {
- if (_fillrule != FillRule.EvenOdd)
- ae1.windCount2 += ae2.windDx;
- else
- ae1.windCount2 = (ae1.windCount2 == 0 ? 1 : 0);
- if (_fillrule != FillRule.EvenOdd)
- ae2.windCount2 -= ae1.windDx;
- else
- ae2.windCount2 = (ae2.windCount2 == 0 ? 1 : 0);
- }
- switch (_fillrule)
- {
- case FillRule.Positive:
- oldE1WindCount = ae1.windCount;
- oldE2WindCount = ae2.windCount;
- break;
- case FillRule.Negative:
- oldE1WindCount = -ae1.windCount;
- oldE2WindCount = -ae2.windCount;
- break;
- default:
- oldE1WindCount = Math.Abs(ae1.windCount);
- oldE2WindCount = Math.Abs(ae2.windCount);
- break;
- }
- bool e1WindCountIs0or1 = oldE1WindCount == 0 || oldE1WindCount == 1;
- bool e2WindCountIs0or1 = oldE2WindCount == 0 || oldE2WindCount == 1;
- if ((!IsHotEdge(ae1) && !e1WindCountIs0or1) || (!IsHotEdge(ae2) && !e2WindCountIs0or1)) return null;
- // NOW PROCESS THE INTERSECTION ...
- // if both edges are 'hot' ...
- if (IsHotEdge(ae1) && IsHotEdge(ae2))
- {
- if ((oldE1WindCount != 0 && oldE1WindCount != 1) || (oldE2WindCount != 0 && oldE2WindCount != 1) ||
- (ae1.localMin.polytype != ae2.localMin.polytype && _cliptype != ClipType.Xor))
- {
- resultOp = AddLocalMaxPoly(ae1, ae2, pt);
- #if USINGZ
- if (resultOp != null)
- SetZ(ae1, ae2, ref resultOp.pt);
- #endif
- }
- else if (IsFront(ae1) || (ae1.outrec == ae2.outrec))
- {
- // this 'else if' condition isn't strictly needed but
- // it's sensible to split polygons that ony touch at
- // a common vertex (not at common edges).
- resultOp = AddLocalMaxPoly(ae1, ae2, pt);
- OutPt op2 = AddLocalMinPoly(ae1, ae2, pt);
- #if USINGZ
- if (resultOp != null)
- SetZ(ae1, ae2, ref resultOp.pt);
- SetZ(ae1, ae2, ref op2.pt);
- #endif
- if (resultOp != null && resultOp.pt == op2.pt &&
- !IsHorizontal(ae1) && !IsHorizontal(ae2) &&
- (InternalClipper.CrossProduct(ae1.bot, resultOp.pt, ae2.bot) == 0))
- AddJoin(resultOp, op2);
- }
- else
- {
- // can't treat as maxima & minima
- resultOp = AddOutPt(ae1, pt);
- #if USINGZ
- OutPt op2 = AddOutPt(ae2, pt);
- SetZ(ae1, ae2, ref resultOp.pt);
- SetZ(ae1, ae2, ref op2.pt);
- #else
- AddOutPt(ae2, pt);
- #endif
- SwapOutrecs(ae1, ae2);
- }
- }
- // if one or other edge is 'hot' ...
- else if (IsHotEdge(ae1))
- {
- resultOp = AddOutPt(ae1, pt);
- #if USINGZ
- SetZ(ae1, ae2, ref resultOp.pt);
- #endif
- SwapOutrecs(ae1, ae2);
- }
- else if (IsHotEdge(ae2))
- {
- resultOp = AddOutPt(ae2, pt);
- #if USINGZ
- SetZ(ae1, ae2, ref resultOp.pt);
- #endif
- SwapOutrecs(ae1, ae2);
- }
- // neither edge is 'hot'
- else
- {
- long e1Wc2, e2Wc2;
- switch (_fillrule)
- {
- case FillRule.Positive:
- e1Wc2 = ae1.windCount2;
- e2Wc2 = ae2.windCount2;
- break;
- case FillRule.Negative:
- e1Wc2 = -ae1.windCount2;
- e2Wc2 = -ae2.windCount2;
- break;
- default:
- e1Wc2 = Math.Abs(ae1.windCount2);
- e2Wc2 = Math.Abs(ae2.windCount2);
- break;
- }
- if (!IsSamePolyType(ae1, ae2))
- {
- resultOp = AddLocalMinPoly(ae1, ae2, pt);
- #if USINGZ
- SetZ(ae1, ae2, ref resultOp.pt);
- #endif
- }
- else if (oldE1WindCount == 1 && oldE2WindCount == 1)
- {
- resultOp = null;
- switch (_cliptype)
- {
- case ClipType.Union:
- if (e1Wc2 > 0 && e2Wc2 > 0) return null;
- resultOp = AddLocalMinPoly(ae1, ae2, pt);
- break;
- case ClipType.Difference:
- if (((GetPolyType(ae1) == PathType.Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
- ((GetPolyType(ae1) == PathType.Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
- {
- resultOp = AddLocalMinPoly(ae1, ae2, pt);
- }
- break;
- case ClipType.Xor:
- resultOp = AddLocalMinPoly(ae1, ae2, pt);
- break;
- default: // ClipType.Intersection:
- if (e1Wc2 <= 0 || e2Wc2 <= 0) return null;
- resultOp = AddLocalMinPoly(ae1, ae2, pt);
- break;
- }
- #if USINGZ
- if (resultOp != null) SetZ(ae1, ae2, ref resultOp.pt);
- #endif
- }
- }
- return resultOp;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DeleteFromAEL(Active ae)
- {
- Active? prev = ae.prevInAEL;
- Active? next = ae.nextInAEL;
- if (prev == null && next == null && (ae != _actives)) return; // already deleted
- if (prev != null)
- prev.nextInAEL = next;
- else
- _actives = next;
- if (next != null) next.prevInAEL = prev;
- // delete &ae;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void AdjustCurrXAndCopyToSEL(long topY)
- {
- Active? ae = _actives;
- _sel = ae;
- while (ae != null)
- {
- ae.prevInSEL = ae.prevInAEL;
- ae.nextInSEL = ae.nextInAEL;
- ae.jump = ae.nextInSEL;
- ae.curX = TopX(ae, topY);
- // NB don't update ae.curr.Y yet (see AddNewIntersectNode)
- ae = ae.nextInAEL;
- }
- }
- protected void ExecuteInternal(ClipType ct, FillRule fillRule)
- {
- if (ct == ClipType.None) return;
- _fillrule = fillRule;
- _cliptype = ct;
- Reset();
- if (!PopScanline(out long y)) return;
- while (_succeeded)
- {
- InsertLocalMinimaIntoAEL(y);
- Active? ae;
- while (PopHorz(out ae)) DoHorizontal(ae!);
- ConvertHorzTrialsToJoins();
- _currentBotY = y; // bottom of scanbeam
- if (!PopScanline(out y))
- break; // y new top of scanbeam
- DoIntersections(y);
- DoTopOfScanbeam(y);
- while (PopHorz(out ae)) DoHorizontal(ae!);
- }
- if (_succeeded) ProcessJoinList();
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DoIntersections(long topY)
- {
- if (BuildIntersectList(topY))
- {
- ProcessIntersectList();
- DisposeIntersectNodes();
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DisposeIntersectNodes()
- {
- _intersectList.Clear();
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void AddNewIntersectNode(Active ae1, Active ae2, long topY)
- {
- if (!InternalClipper.GetIntersectPt(
- ae1.bot, ae1.top, ae2.bot, ae2.top, out Point64 ip))
- ip = new Point64(ae1.curX, topY);
- if (ip.Y > _currentBotY || ip.Y < topY)
- {
- double absDx1 = Math.Abs(ae1.dx);
- double absDx2 = Math.Abs(ae2.dx);
- if (absDx1 > 100 && absDx2 > 100)
- {
- if (absDx1 > absDx2)
- ip = InternalClipper.GetClosestPtOnSegment(ip, ae1.bot, ae1.top);
- else
- ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top);
- }
- else if (absDx1 > 100)
- ip = InternalClipper.GetClosestPtOnSegment(ip, ae1.bot, ae1.top);
- else if (absDx2 > 100)
- ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top);
- else
- {
- if (ip.Y < topY) ip.Y = topY;
- else ip.Y = _currentBotY;
- if (absDx1 < absDx2) ip.X = TopX(ae1, ip.Y);
- else ip.X = TopX(ae2, topY);
- }
- }
- IntersectNode node = new IntersectNode(ip, ae1, ae2);
- _intersectList.Add(node);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Active? ExtractFromSEL(Active ae)
- {
- Active? res = ae.nextInSEL;
- if (res != null)
- res.prevInSEL = ae.prevInSEL;
- ae.prevInSEL!.nextInSEL = res;
- return res;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void Insert1Before2InSEL(Active ae1, Active ae2)
- {
- ae1.prevInSEL = ae2.prevInSEL;
- if (ae1.prevInSEL != null)
- ae1.prevInSEL.nextInSEL = ae1;
- ae1.nextInSEL = ae2;
- ae2.prevInSEL = ae1;
- }
- private bool BuildIntersectList(long topY)
- {
- if (_actives == null || _actives.nextInAEL == null) return false;
- // 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);
- // 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.
- // Re merge sorts see https://stackoverflow.com/a/46319131/359538
- Active? left = _sel, right, lEnd, rEnd, currBase, prevBase, tmp;
- while (left!.jump != null)
- {
- prevBase = null;
- while (left != null && left.jump != null)
- {
- currBase = left;
- right = left.jump;
- lEnd = right;
- rEnd = right.jump;
- left.jump = rEnd;
- while (left != lEnd && right != rEnd)
- {
- if (right!.curX < left!.curX)
- {
- tmp = right.prevInSEL!;
- for (; ; )
- {
- AddNewIntersectNode(tmp, right, topY);
- if (tmp == left) break;
- tmp = tmp.prevInSEL!;
- }
- tmp = right;
- right = ExtractFromSEL(tmp);
- lEnd = right;
- Insert1Before2InSEL(tmp, left);
- if (left == currBase)
- {
- currBase = tmp;
- currBase.jump = rEnd;
- if (prevBase == null) _sel = currBase;
- else prevBase.jump = currBase;
- }
- }
- else left = left.nextInSEL;
- }
- prevBase = currBase;
- left = rEnd;
- }
- left = _sel;
- }
- return _intersectList.Count > 0;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void ProcessIntersectList()
- {
- // We now have a list of intersections required so that edges will be
- // correctly positioned at the top of the scanbeam. However, it's important
- // that edge intersections are processed from the bottom up, but it's also
- // crucial that intersections only occur between adjacent edges.
- // First we do a quicksort so intersections proceed in a bottom up order ...
- _intersectList.Sort(new IntersectListSort());
- // Now as we process these intersections, we must sometimes adjust the order
- // to ensure that intersecting edges are always adjacent ...
- for (int i = 0; i < _intersectList.Count; ++i)
- {
- if (!EdgesAdjacentInAEL(_intersectList[i]))
- {
- int j = i + 1;
- while (!EdgesAdjacentInAEL(_intersectList[j])) j++;
- // swap
- (_intersectList[j], _intersectList[i]) =
- (_intersectList[i], _intersectList[j]);
- }
- IntersectNode node = _intersectList[i];
- IntersectEdges(node.edge1, node.edge2, node.pt);
- SwapPositionsInAEL(node.edge1, node.edge2);
- if (TestJoinWithPrev2(node.edge2, node.pt))
- {
- OutPt op1 = AddOutPt(node.edge2.prevInAEL!, node.pt);
- OutPt op2 = AddOutPt(node.edge2, node.pt);
- if (op1 != op2) AddJoin(op1, op2);
- }
- else if (TestJoinWithNext2(node.edge1, node.pt))
- {
- OutPt op1 = AddOutPt(node.edge1, node.pt);
- OutPt op2 = AddOutPt(node.edge1.nextInAEL!, node.pt);
- if (op1 != op2) AddJoin(op1, op2);
- }
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void SwapPositionsInAEL(Active ae1, Active ae2)
- {
- // preconditon: ae1 must be immediately to the left of ae2
- Active? next = ae2.nextInAEL;
- if (next != null) next.prevInAEL = ae1;
- Active? prev = ae1.prevInAEL;
- if (prev != null) prev.nextInAEL = ae2;
- ae2.prevInAEL = prev;
- ae2.nextInAEL = ae1;
- ae1.prevInAEL = ae2;
- ae1.nextInAEL = next;
- if (ae2.prevInAEL == null) _actives = ae2;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool ResetHorzDirection(Active horz, Active? maxPair,
- out long leftX, out long rightX)
- {
- if (horz.bot.X == horz.top.X)
- {
- // the horizontal edge is going nowhere ...
- leftX = horz.curX;
- rightX = horz.curX;
- Active? ae = horz.nextInAEL;
- while (ae != null && ae != maxPair) ae = ae.nextInAEL;
- return ae != null;
- }
- if (horz.curX < horz.top.X)
- {
- leftX = horz.curX;
- rightX = horz.top.X;
- return true;
- }
- leftX = horz.top.X;
- rightX = horz.curX;
- return false; // right to left
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool HorzIsSpike(Active horz)
- {
- Point64 nextPt = NextVertex(horz).pt;
- return (horz.bot.X < horz.top.X) != (horz.top.X < nextPt.X);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void TrimHorz(Active horzEdge, bool preserveCollinear)
- {
- bool wasTrimmed = false;
- Point64 pt = NextVertex(horzEdge).pt;
- while (pt.Y == horzEdge.top.Y)
- {
- // always trim 180 deg. spikes (in closed paths)
- // but otherwise break if preserveCollinear = true
- if (preserveCollinear &&
- (pt.X < horzEdge.top.X) != (horzEdge.bot.X < horzEdge.top.X))
- break;
- horzEdge.vertexTop = NextVertex(horzEdge);
- horzEdge.top = pt;
- wasTrimmed = true;
- if (IsMaxima(horzEdge)) break;
- pt = NextVertex(horzEdge).pt;
- }
- if (wasTrimmed) SetDx(horzEdge); // +/-infinity
- }
- private void DoHorizontal(Active horz)
- /*******************************************************************************
- * Notes: Horizontal edges (HEs) at scanline intersections (i.e. at the top or *
- * bottom of a scanbeam) are processed as if layered.The order in which HEs *
- * are processed doesn't matter. HEs intersect with the bottom vertices of *
- * other HEs[#] and with non-horizontal edges [*]. Once these intersections *
- * are completed, intermediate HEs are 'promoted' to the next edge in their *
- * bounds, and they in turn may be intersected[%] by other HEs. *
- * *
- * eg: 3 horizontals at a scanline: / | / / *
- * | / | (HE3)o ========%========== o *
- * o ======= o(HE2) / | / / *
- * o ============#=========*======*========#=========o (HE1) *
- * / | / | / *
- *******************************************************************************/
- {
- Point64 pt;
- bool horzIsOpen = IsOpen(horz);
- long Y = horz.bot.Y;
- Vertex? vertex_max = null;
- Active? maxPair = null;
- if (!horzIsOpen)
- {
- vertex_max = GetCurrYMaximaVertex(horz);
- if (vertex_max != null)
- {
- maxPair = GetHorzMaximaPair(horz, vertex_max);
- // remove 180 deg.spikes and also simplify
- // consecutive horizontals when PreserveCollinear = true
- if (vertex_max != horz.vertexTop)
- TrimHorz(horz, PreserveCollinear);
- }
- }
- bool isLeftToRight =
- ResetHorzDirection(horz, maxPair, out long leftX, out long rightX);
- if (IsHotEdge(horz))
- AddOutPt(horz, new Point64(horz.curX, Y));
- OutPt? op;
- for (; ; )
- {
- if (horzIsOpen && IsMaxima(horz) && !IsOpenEnd(horz))
- {
- vertex_max = GetCurrYMaximaVertex(horz);
- if (vertex_max != null)
- maxPair = GetHorzMaximaPair(horz, vertex_max);
- }
- // loops through consec. horizontal edges (if open)
- Active? ae;
- if (isLeftToRight) ae = horz.nextInAEL;
- else ae = horz.prevInAEL;
- while (ae != null)
- {
- if (ae == maxPair)
- {
- if (IsHotEdge(horz))
- {
- while (horz.vertexTop != ae.vertexTop)
- {
- AddOutPt(horz, horz.top);
- UpdateEdgeIntoAEL(horz);
- }
- op = AddLocalMaxPoly(horz, ae, horz.top);
- if (op != null && !IsOpen(horz) && op.pt == horz.top)
- AddTrialHorzJoin(op);
- }
- DeleteFromAEL(ae);
- DeleteFromAEL(horz);
- return;
- }
- // if horzEdge is a maxima, keep going until we reach
- // its maxima pair, otherwise check for break conditions
- if (vertex_max != horz.vertexTop || IsOpenEnd(horz))
- {
- // otherwise stop when 'ae' is beyond the end of the horizontal line
- if ((isLeftToRight && ae.curX > rightX) ||
- (!isLeftToRight && ae.curX < leftX)) break;
- if (ae.curX == horz.top.X && !IsHorizontal(ae))
- {
- pt = NextVertex(horz).pt;
- if (isLeftToRight)
- {
- // with open paths we'll only break once past horz's end
- if (IsOpen(ae) && !IsSamePolyType(ae, horz) && !IsHotEdge(ae))
- {
- if (TopX(ae, pt.Y) > pt.X) break;
- }
- // otherwise we'll only break when horz's outslope is greater than e's
- else if (TopX(ae, pt.Y) >= pt.X) break;
- }
- else
- {
- // with open paths we'll only break once past horz's end
- if (IsOpen(ae) && !IsSamePolyType(ae, horz) && !IsHotEdge(ae))
- {
- if (TopX(ae, pt.Y) < pt.X) break;
- }
- // otherwise we'll only break when horz's outslope is greater than e's
- else if (TopX(ae, pt.Y) <= pt.X) break;
- }
- }
- }
- pt = new Point64(ae.curX, Y);
- if (isLeftToRight)
- {
- op = IntersectEdges(horz, ae, pt);
- SwapPositionsInAEL(horz, ae);
- if (IsHotEdge(horz) && op != null &&
- !IsOpen(horz) && op.pt == pt)
- AddTrialHorzJoin(op);
- if (!IsHorizontal(ae) && TestJoinWithPrev1(ae))
- {
- op = AddOutPt(ae.prevInAEL!, pt);
- OutPt op2 = AddOutPt(ae, pt);
- AddJoin(op, op2);
- }
- horz.curX = ae.curX;
- ae = horz.nextInAEL;
- }
- else
- {
- op = IntersectEdges(ae, horz, pt);
- SwapPositionsInAEL(ae, horz);
- if (IsHotEdge(horz) && op != null &&
- !IsOpen(horz) && op.pt == pt)
- AddTrialHorzJoin(op);
- if (!IsHorizontal(ae) && TestJoinWithNext1(ae))
- {
- op = AddOutPt(ae, pt);
- OutPt op2 = AddOutPt(ae.nextInAEL!, pt);
- AddJoin(op, op2);
- }
- horz.curX = ae.curX;
- ae = horz.prevInAEL;
- }
- } // we've reached the end of this horizontal
- // check if we've finished looping through consecutive horizontals
- if (horzIsOpen && IsOpenEnd(horz))
- {
- if (IsHotEdge(horz))
- {
- AddOutPt(horz, horz.top);
- if (IsFront(horz))
- horz.outrec!.frontEdge = null;
- else
- horz.outrec!.backEdge = null;
- }
- horz.outrec = null;
- DeleteFromAEL(horz); // ie open at top
- return;
- }
- if (NextVertex(horz).pt.Y != horz.top.Y) break;
- // there must be a following (consecutive) horizontal
- if (IsHotEdge(horz))
- AddOutPt(horz, horz.top);
- UpdateEdgeIntoAEL(horz);
- if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz))
- TrimHorz(horz, true);
- isLeftToRight = ResetHorzDirection(horz, maxPair, out leftX, out rightX);
- } // end for loop and end of (possible consecutive) horizontals
- if (IsHotEdge(horz))
- {
- op = AddOutPt(horz, horz.top);
- if (!IsOpen(horz))
- AddTrialHorzJoin(op);
- }
- else
- op = null;
- if ((horzIsOpen && !IsOpenEnd(horz)) ||
- (!horzIsOpen && vertex_max != horz.vertexTop))
- {
- UpdateEdgeIntoAEL(horz); // this is the end of an intermediate horiz.
- if (IsOpen(horz)) return;
- if (isLeftToRight && TestJoinWithNext1(horz))
- {
- OutPt op2 = AddOutPt(horz.nextInAEL!, horz.bot);
- AddJoin(op!, op2);
- }
- else if (!isLeftToRight && TestJoinWithPrev1(horz))
- {
- OutPt op2 = AddOutPt(horz.prevInAEL!, horz.bot);
- AddJoin(op2, op!);
- }
- }
- else if (IsHotEdge(horz))
- AddLocalMaxPoly(horz, maxPair!, horz.top);
- else
- {
- DeleteFromAEL(maxPair!);
- DeleteFromAEL(horz);
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DoTopOfScanbeam(long y)
- {
- _sel = null; // sel_ is reused to flag horizontals (see PushHorz below)
- Active? ae = _actives;
- while (ae != null)
- {
- // NB 'ae' will never be horizontal here
- if (ae.top.Y == y)
- {
- ae.curX = ae.top.X;
- if (IsMaxima(ae))
- {
- ae = DoMaxima(ae); // TOP OF BOUND (MAXIMA)
- continue;
- }
- // INTERMEDIATE VERTEX ...
- if (IsHotEdge(ae))
- AddOutPt(ae, ae.top);
- UpdateEdgeIntoAEL(ae);
- if (IsHorizontal(ae))
- PushHorz(ae); // horizontals are processed later
- }
- else // i.e. not the top of the edge
- ae.curX = TopX(ae, y);
- ae = ae.nextInAEL;
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private Active? DoMaxima(Active ae)
- {
- Active? prevE;
- Active? nextE, maxPair;
- prevE = ae.prevInAEL;
- nextE = ae.nextInAEL;
- if (IsOpenEnd(ae))
- {
- if (IsHotEdge(ae)) AddOutPt(ae, ae.top);
- if (!IsHorizontal(ae))
- {
- if (IsHotEdge(ae))
- {
- if (IsFront(ae))
- ae.outrec!.frontEdge = null;
- else
- ae.outrec!.backEdge = null;
- ae.outrec = null;
- }
- DeleteFromAEL(ae);
- }
- return nextE;
- }
- maxPair = GetMaximaPair(ae);
- if (maxPair == null) return nextE; // eMaxPair is horizontal
- // only non-horizontal maxima here.
- // process any edges between maxima pair ...
- while (nextE != maxPair)
- {
- IntersectEdges(ae, nextE!, ae.top);
- SwapPositionsInAEL(ae, nextE!);
- nextE = ae.nextInAEL;
- }
- if (IsOpen(ae))
- {
- if (IsHotEdge(ae))
- AddLocalMaxPoly(ae, maxPair, ae.top);
- DeleteFromAEL(maxPair);
- DeleteFromAEL(ae);
- return (prevE != null ? prevE.nextInAEL : _actives);
- }
- // here ae.nextInAel == ENext == EMaxPair ...
- if (IsHotEdge(ae))
- AddLocalMaxPoly(ae, maxPair, ae.top);
- DeleteFromAEL(ae);
- DeleteFromAEL(maxPair);
- return (prevE != null ? prevE.nextInAEL : _actives);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsValidPath(OutPt op)
- {
- return (op.next != op);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool PtsReallyClose(Point64 pt1, Point64 pt2)
- {
- return (Math.Abs(pt1.X - pt2.X) < 2) && (Math.Abs(pt1.Y - pt2.Y) < 2);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsVerySmallTriangle(OutPt op)
- {
- return op.next!.next == op.prev &&
- (PtsReallyClose(op.prev.pt, op.next.pt) ||
- PtsReallyClose(op.pt, op.next.pt) ||
- PtsReallyClose(op.pt, op.prev.pt));
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsValidClosedPath(OutPt? op)
- {
- return (op != null && op.next != op &&
- (op.next != op.prev || !IsVerySmallTriangle(op)));
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool ValueBetween(long val, long end1, long end2)
- {
- // NB accommodates axis aligned between where end1 == end2
- return ((val != end1) == (val != end2)) &&
- ((val > end1) == (val < end2));
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool ValueEqualOrBetween(long val, long end1, long end2)
- {
- return (val == end1) || (val == end2) || ((val > end1) == (val < end2));
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool PointEqualOrBetween(Point64 pt, Point64 corner1, Point64 corner2)
- {
- // NB points may not be collinear
- return
- ValueEqualOrBetween(pt.X, corner1.X, corner2.X) &&
- ValueEqualOrBetween(pt.Y, corner1.Y, corner2.Y);
- }
- private static bool PointBetween(Point64 pt, Point64 corner1, Point64 corner2)
- {
- // NB points may not be collinear
- return
- ValueBetween(pt.X, corner1.X, corner2.X) &&
- ValueBetween(pt.Y, corner1.Y, corner2.Y);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool CollinearSegsOverlap(Point64 seg1a, Point64 seg1b,
- Point64 seg2a, Point64 seg2b)
- {
- // precondition: seg1 and seg2 are collinear
- if (seg1a.X == seg1b.X)
- {
- if (seg2a.X != seg1a.X || seg2a.X != seg2b.X) return false;
- }
- else if (seg1a.X < seg1b.X)
- {
- if (seg2a.X < seg2b.X)
- {
- if (seg2a.X >= seg1b.X || seg2b.X <= seg1a.X) return false;
- }
- else
- {
- if (seg2b.X >= seg1b.X || seg2a.X <= seg1a.X) return false;
- }
- }
- else
- {
- if (seg2a.X < seg2b.X)
- {
- if (seg2a.X >= seg1a.X || seg2b.X <= seg1b.X) return false;
- }
- else
- {
- if (seg2b.X >= seg1a.X || seg2a.X <= seg1b.X) return false;
- }
- }
- if (seg1a.Y == seg1b.Y)
- {
- if (seg2a.Y != seg1a.Y || seg2a.Y != seg2b.Y) return false;
- }
- else if (seg1a.Y < seg1b.Y)
- {
- if (seg2a.Y < seg2b.Y)
- {
- if (seg2a.Y >= seg1b.Y || seg2b.Y <= seg1a.Y) return false;
- }
- else
- {
- if (seg2b.Y >= seg1b.Y || seg2a.Y <= seg1a.Y) return false;
- }
- }
- else
- {
- if (seg2a.Y < seg2b.Y)
- {
- if (seg2a.Y >= seg1a.Y || seg2b.Y <= seg1b.Y) return false;
- }
- else
- {
- if (seg2b.Y >= seg1a.Y || seg2a.Y <= seg1b.Y) return false;
- }
- }
- return true;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool HorzEdgesOverlap(long x1a, long x1b, long x2a, long x2b)
- {
- const long minOverlap = 2;
- if (x1a > x1b + minOverlap)
- {
- if (x2a > x2b + minOverlap)
- return !((x1a <= x2b) || (x2a <= x1b));
- return !((x1a <= x2a) || (x2b <= x1b));
- }
- if (x1b > x1a + minOverlap)
- {
- if (x2a > x2b + minOverlap)
- return !((x1b <= x2b) || (x2a <= x1a));
- return !((x1b <= x2a) || (x2b <= x1a));
- }
- return false;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Joiner? GetHorzTrialParent(OutPt op)
- {
- Joiner? joiner = op.joiner;
- while (joiner != null)
- {
- if (joiner.op1 == op)
- {
- if (joiner.next1 != null &&
- joiner.next1.idx < 0) return joiner;
- joiner = joiner.next1;
- }
- else
- {
- if (joiner.next2 != null &&
- joiner.next2.idx < 0) return joiner;
- joiner = joiner.next1;
- }
- }
- return joiner;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool OutPtInTrialHorzList(OutPt op)
- {
- return op.joiner != null &&
- ((op.joiner.idx < 0) || GetHorzTrialParent(op) != null);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool ValidateClosedPathEx(ref OutPt? op)
- {
- if (IsValidClosedPath(op)) return true;
- if (op != null)
- SafeDisposeOutPts(ref op);
- return false;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static OutPt InsertOp(Point64 pt, OutPt insertAfter)
- {
- OutPt result = new OutPt(pt, insertAfter.outrec)
- { next = insertAfter.next };
- insertAfter.next!.prev = result;
- insertAfter.next = result;
- result.prev = insertAfter;
- return result;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static OutPt? DisposeOutPt(OutPt op)
- {
- OutPt? result = (op.next == op ? null : op.next);
- op.prev.next = op.next;
- op.next!.prev = op.prev;
- // op == null;
- return result;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void SafeDisposeOutPts(ref OutPt op)
- {
- OutRec? outRec = GetRealOutRec(op.outrec);
- if (outRec!.frontEdge != null)
- outRec.frontEdge.outrec = null;
- if (outRec.backEdge != null)
- outRec.backEdge.outrec = null;
- op.prev.next = null;
- OutPt? op2 = op;
- while (op2 != null)
- {
- SafeDeleteOutPtJoiners(op2);
- op2 = op2.next;
- }
- outRec.pts = null;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void SafeDeleteOutPtJoiners(OutPt op)
- {
- Joiner? joiner = op.joiner;
- if (joiner == null) return;
- while (joiner != null)
- {
- if (joiner.idx < 0)
- DeleteTrialHorzJoin(op);
- else if (_horzJoiners != null)
- {
- if (OutPtInTrialHorzList(joiner.op1))
- DeleteTrialHorzJoin(joiner.op1);
- if (OutPtInTrialHorzList(joiner.op2!))
- DeleteTrialHorzJoin(joiner.op2!);
- DeleteJoin(joiner);
- }
- else
- DeleteJoin(joiner);
- joiner = op.joiner;
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void AddTrialHorzJoin(OutPt op)
- {
- // make sure 'op' isn't added more than once
- if (!op.outrec.isOpen && !OutPtInTrialHorzList(op))
- _horzJoiners = new Joiner(op, null, _horzJoiners);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Joiner? FindTrialJoinParent(ref Joiner joiner, OutPt op)
- {
- Joiner? parent = joiner;
- while (parent != null)
- {
- if (op == parent.op1)
- {
- if (parent.next1 != null && parent.next1.idx < 0)
- {
- joiner = parent.next1;
- return parent;
- }
- parent = parent.next1;
- }
- else
- {
- if (parent.next2 != null && parent.next2.idx < 0)
- {
- joiner = parent.next2;
- return parent;
- }
- parent = parent.next2;
- }
- }
- return null;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DeleteTrialHorzJoin(OutPt op)
- {
- if (_horzJoiners == null) return;
- Joiner? joiner = op.joiner;
- Joiner? parentH, parentOp = null;
- while (joiner != null)
- {
- if (joiner.idx < 0)
- {
- // first remove joiner from FHorzTrials
- if (joiner == _horzJoiners)
- _horzJoiners = joiner.nextH;
- else
- {
- parentH = _horzJoiners;
- while (parentH!.nextH != joiner)
- parentH = parentH.nextH;
- parentH.nextH = joiner.nextH;
- }
- // now remove joiner from op's joiner list
- if (parentOp == null)
- {
- // joiner must be first one in list
- op.joiner = joiner.next1;
- // joiner == null;
- joiner = op.joiner;
- }
- else
- {
- // the trial joiner isn't first
- if (op == parentOp.op1)
- parentOp.next1 = joiner.next1;
- else
- parentOp.next2 = joiner.next1;
- // joiner = null;
- joiner = parentOp;
- }
- }
- else
- {
- // not a trial join so look further along the linked list
- parentOp = FindTrialJoinParent(ref joiner, op);
- if (parentOp == null) break;
- }
- // loop in case there's more than one trial join
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool GetHorzExtendedHorzSeg(ref OutPt op, out OutPt op2)
- {
- OutRec outRec = GetRealOutRec(op.outrec)!;
- op2 = op;
- if (outRec.frontEdge != null)
- {
- while (op.prev != outRec.pts &&
- op.prev.pt.Y == op.pt.Y) op = op.prev;
- while (op2 != outRec.pts &&
- op2.next!.pt.Y == op2.pt.Y) op2 = op2.next;
- return op2 != op;
- }
- while (op.prev != op2 && op.prev.pt.Y == op.pt.Y)
- op = op.prev;
- while (op2.next != op && op2.next!.pt.Y == op2.pt.Y)
- op2 = op2.next;
- return op2 != op && op2.next != op;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void ConvertHorzTrialsToJoins()
- {
- while (_horzJoiners != null)
- {
- Joiner? joiner = _horzJoiners;
- _horzJoiners = _horzJoiners.nextH;
- OutPt op1a = joiner.op1;
- if (op1a.joiner == joiner)
- {
- op1a.joiner = joiner.next1;
- }
- else
- {
- Joiner joinerParent = FindJoinParent(joiner, op1a);
- if (joinerParent.op1 == op1a)
- joinerParent.next1 = joiner.next1;
- else
- joinerParent.next2 = joiner.next1;
- }
- // joiner = null;
- if (!GetHorzExtendedHorzSeg(ref op1a, out OutPt op1b))
- {
- if (op1a.outrec.frontEdge == null)
- CleanCollinear(op1a.outrec);
- continue;
- }
- OutPt op2a;
- bool joined = false;
- joiner = _horzJoiners;
- while (joiner != null)
- {
- op2a = joiner.op1;
- if (GetHorzExtendedHorzSeg(ref op2a, out OutPt op2b) &&
- HorzEdgesOverlap(op1a.pt.X, op1b.pt.X, op2a.pt.X, op2b.pt.X))
- {
- // overlap found so promote to a 'real' join
- joined = true;
- if (op1a.pt == op2b.pt)
- AddJoin(op1a, op2b);
- else if (op1b.pt == op2a.pt)
- AddJoin(op1b, op2a);
- else if (op1a.pt == op2a.pt)
- AddJoin(op1a, op2a);
- else if (op1b.pt == op2b.pt)
- AddJoin(op1b, op2b);
- else if (ValueBetween(op1a.pt.X, op2a.pt.X, op2b.pt.X))
- AddJoin(op1a, InsertOp(op1a.pt, op2a));
- else if (ValueBetween(op1b.pt.X, op2a.pt.X, op2b.pt.X))
- AddJoin(op1b, InsertOp(op1b.pt, op2a));
- else if (ValueBetween(op2a.pt.X, op1a.pt.X, op1b.pt.X))
- AddJoin(op2a, InsertOp(op2a.pt, op1a));
- else if (ValueBetween(op2b.pt.X, op1a.pt.X, op1b.pt.X))
- AddJoin(op2b, InsertOp(op2b.pt, op1a));
- break;
- }
- joiner = joiner.nextH;
- }
- if (!joined)
- CleanCollinear(op1a.outrec);
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void AddJoin(OutPt op1, OutPt op2)
- {
- if ((op1.outrec == op2.outrec) && ((op1 == op2) ||
- // unless op1.next or op1.prev crosses the start-end divide
- // don't waste time trying to join adjacent vertices
- ((op1.next == op2) && (op1 != op1.outrec.pts)) ||
- ((op2.next == op1) && (op2 != op1.outrec.pts)))) return;
- Joiner joiner = new Joiner(op1, op2, null) { idx = _joinerList.Count };
- _joinerList.Add(joiner);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Joiner FindJoinParent(Joiner joiner, OutPt op)
- {
- Joiner result = op.joiner!;
- for (; ; )
- {
- if (op == result.op1)
- {
- if (result.next1 == joiner) return result;
- result = result.next1!;
- }
- else
- {
- if (result.next2 == joiner) return result;
- result = result.next2!;
- }
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DeleteJoin(Joiner joiner)
- {
- // This method deletes a single join, and it doesn't check for or
- // delete trial horz. joins. For that, use the following method.
- OutPt op1 = joiner.op1, op2 = joiner.op2!;
- Joiner parentJnr;
- if (op1.joiner != joiner)
- {
- parentJnr = FindJoinParent(joiner, op1);
- if (parentJnr.op1 == op1)
- parentJnr.next1 = joiner.next1;
- else
- parentJnr.next2 = joiner.next1;
- }
- else
- op1.joiner = joiner.next1;
- if (op2.joiner != joiner)
- {
- parentJnr = FindJoinParent(joiner, op2);
- if (parentJnr.op1 == op2)
- parentJnr.next1 = joiner.next2;
- else
- parentJnr.next2 = joiner.next2;
- }
- else
- op2.joiner = joiner.next2;
- _joinerList[joiner.idx] = null;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void ProcessJoinList()
- {
- // NB can't use foreach here because list may
- // contain nulls which can't be enumerated
- for (int i = 0; i < _joinerList.Count; i++)
- {
- Joiner? j = _joinerList[i];
- if (j == null) continue;
- OutRec outrec = ProcessJoin(j);
- CleanCollinear(outrec);
- }
- _joinerList.Clear();
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool CheckDisposeAdjacent(ref OutPt op, OutPt guard, OutRec outRec)
- {
- bool result = false;
- while (op.prev != op)
- {
- if (op.pt == op.prev.pt && op != guard &&
- op.prev.joiner != null && op.joiner == null)
- {
- if (op == outRec.pts) outRec.pts = op.prev;
- op = DisposeOutPt(op)!;
- op = op.prev;
- }
- else
- break;
- }
- while (op.next != op)
- {
- if (op.pt == op.next!.pt && op != guard &&
- op.next.joiner != null && op.joiner == null)
- {
- if (op == outRec.pts) outRec.pts = op.prev;
- op = DisposeOutPt(op)!;
- op = op.prev;
- }
- else
- break;
- }
- return result;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static double DistanceFromLineSqrd(Point64 pt, Point64 linePt1, Point64 linePt2)
- {
- // perpendicular distance of point (x0,y0) = (a*x0 + b*y0 + C)/Sqrt(a*a + b*b)
- // where ax + by +c = 0 is the equation of the line
- // see https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
- double a = (linePt1.Y - linePt2.Y);
- double b = (linePt2.X - linePt1.X);
- double c = a * linePt1.X + b * linePt1.Y;
- double q = a * pt.X + b * pt.Y - c;
- return (q * q) / (a * a + b * b);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static double DistanceSqr(Point64 pt1, Point64 pt2)
- {
- return (double)(pt1.X - pt2.X) * (pt1.X - pt2.X) +
- (double)(pt1.Y - pt2.Y) * (pt1.Y - pt2.Y);
- }
- private OutRec ProcessJoin(Joiner j)
- {
- OutPt op1 = j.op1, op2 = j.op2!;
- OutRec or1 = GetRealOutRec(op1.outrec)!;
- OutRec or2 = GetRealOutRec(op2.outrec)!;
- DeleteJoin(j);
- if (or2.pts == null) return or1;
- if (!IsValidClosedPath(op2))
- {
- SafeDisposeOutPts(ref op2);
- return or1;
- }
- if ((or1.pts == null) || !IsValidClosedPath(op1))
- {
- SafeDisposeOutPts(ref op1);
- return or2;
- }
- if (or1 == or2 &&
- ((op1 == op2) || (op1.next == op2) || (op1.prev == op2))) return or1;
- CheckDisposeAdjacent(ref op1, op2, or1);
- CheckDisposeAdjacent(ref op2, op1, or2);
- if (op1.next == op2 || op2.next == op1) return or1;
- OutRec result = or1;
- for (; ; )
- {
- if (!IsValidPath(op1) || !IsValidPath(op2) ||
- (or1 == or2 && (op1.prev == op2 || op1.next == op2))) return or1;
- if (op1.prev.pt == op2.next!.pt ||
- ((InternalClipper.CrossProduct(op1.prev.pt, op1.pt, op2.next.pt) == 0) &&
- CollinearSegsOverlap(op1.prev.pt, op1.pt, op2.pt, op2.next.pt)))
- {
- if (or1 == or2)
- {
- // SPLIT REQUIRED
- // make sure op1.prev and op2.next match positions
- // by inserting an extra vertex if needed
- if (op1.prev.pt != op2.next.pt)
- {
- if (PointEqualOrBetween(op1.prev.pt, op2.pt, op2.next.pt))
- op2.next = InsertOp(op1.prev.pt, op2);
- else
- op1.prev = InsertOp(op2.next.pt, op1.prev);
- }
- // current to new
- // op1.p[opA] >>> op1 ... opA \ / op1
- // op2.n[opB] <<< op2 ... opB / \ op2
- OutPt opA = op1.prev, opB = op2.next;
- opA.next = opB;
- opB.prev = opA;
- op1.prev = op2;
- op2.next = op1;
- CompleteSplit(op1, opA, or1);
- }
- else
- {
- // JOIN, NOT SPLIT
- OutPt opA = op1.prev, opB = op2.next;
- opA.next = opB;
- opB.prev = opA;
- op1.prev = op2;
- op2.next = op1;
- //SafeDeleteOutPtJoiners(op2);
- //DisposeOutPt(op2);
- if (or1.idx < or2.idx)
- {
- or1.pts = op1;
- or2.pts = null;
- if (or1.owner != null &&
- (or2.owner == null || or2.owner.idx < or1.owner.idx))
- {
- or1.owner = or2.owner;
- }
- or2.owner = or1;
- }
- else
- {
- result = or2;
- or2.pts = op1;
- or1.pts = null;
- if (or2.owner != null &&
- (or1.owner == null || or1.owner.idx < or2.owner.idx))
- {
- or2.owner = or1.owner;
- }
- or1.owner = or2;
- }
- }
- break;
- }
- if (op1.next!.pt == op2.prev.pt ||
- ((InternalClipper.CrossProduct(op1.next.pt, op2.pt, op2.prev.pt) == 0) &&
- CollinearSegsOverlap(op1.next.pt, op1.pt, op2.pt, op2.prev.pt)))
- {
- if (or1 == or2)
- {
- // SPLIT REQUIRED
- // make sure op2.prev and op1.next match positions
- // by inserting an extra vertex if needed
- if (op2.prev.pt != op1.next.pt)
- {
- if (PointEqualOrBetween(op2.prev.pt, op1.pt, op1.next.pt))
- op1.next = InsertOp(op2.prev.pt, op1);
- else
- op2.prev = InsertOp(op1.next.pt, op2.prev);
- }
- // current to new
- // op2.p[opA] >>> op2 ... opA \ / op2
- // op1.n[opB] <<< op1 ... opB / \ op1
- OutPt opA = op2.prev, opB = op1.next;
- opA.next = opB;
- opB.prev = opA;
- op2.prev = op1;
- op1.next = op2;
- CompleteSplit(op1, opA, or1);
- }
- else
- {
- // JOIN, NOT SPLIT
- OutPt opA = op1.next, opB = op2.prev;
- opA.prev = opB;
- opB.next = opA;
- op1.next = op2;
- op2.prev = op1;
- //SafeDeleteOutPtJoiners(op2);
- //DisposeOutPt(op2);
- if (or1.idx < or2.idx)
- {
- or1.pts = op1;
- or2.pts = null;
- if (or1.owner != null &&
- (or2.owner == null || or2.owner.idx < or1.owner.idx))
- {
- or1.owner = or2.owner;
- }
- or2.owner = or1;
- }
- else
- {
- result = or2;
- or2.pts = op1;
- or1.pts = null;
- if (or2.owner != null &&
- (or1.owner == null || or1.owner.idx < or2.owner.idx))
- {
- or2.owner = or1.owner;
- }
- or1.owner = or2;
- }
- }
- break;
- }
- if (PointBetween(op1.next.pt, op2.pt, op2.prev.pt) &&
- DistanceFromLineSqrd(op1.next.pt, op2.pt, op2.prev.pt) < 2.01)
- {
- InsertOp(op1.next.pt, op2.prev);
- continue;
- }
- if (PointBetween(op2.next.pt, op1.pt, op1.prev.pt) &&
- DistanceFromLineSqrd(op2.next.pt, op1.pt, op1.prev.pt) < 2.01)
- {
- InsertOp(op2.next.pt, op1.prev);
- continue;
- }
- if (PointBetween(op1.prev.pt, op2.pt, op2.next.pt) &&
- DistanceFromLineSqrd(op1.prev.pt, op2.pt, op2.next.pt) < 2.01)
- {
- InsertOp(op1.prev.pt, op2);
- continue;
- }
- if (PointBetween(op2.prev.pt, op1.pt, op1.next.pt) &&
- DistanceFromLineSqrd(op2.prev.pt, op1.pt, op1.next.pt) < 2.01)
- {
- InsertOp(op2.prev.pt, op1);
- continue;
- }
- // something odd needs tidying up
- if (CheckDisposeAdjacent(ref op1, op2, or1)) continue;
- if (CheckDisposeAdjacent(ref op2, op1, or1)) continue;
- if (op1.prev.pt != op2.next!.pt &&
- (DistanceSqr(op1.prev.pt, op2.next.pt) < 2.01))
- {
- op1.prev.pt = op2.next.pt;
- continue;
- }
- if (op1.next!.pt != op2.prev.pt &&
- (DistanceSqr(op1.next.pt, op2.prev.pt) < 2.01))
- {
- op2.prev.pt = op1.next.pt;
- continue;
- }
- // OK, there doesn't seem to be a way to join after all
- // so just tidy up the polygons
- or1.pts = op1;
- if (or2 != or1)
- {
- or2.pts = op2;
- CleanCollinear(or2);
- }
- break;
- }
- return result;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void UpdateOutrecOwner(OutRec outrec)
- {
- OutPt opCurr = outrec.pts!;
- for (; ; )
- {
- opCurr.outrec = outrec;
- opCurr = opCurr.next!;
- if (opCurr == outrec.pts) return;
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void CompleteSplit(OutPt? op1, OutPt? op2, OutRec outrec)
- {
- double area1 = Area(op1!);
- double area2 = Area(op2!);
- bool signs_change = (area1 > 0) == (area2 < 0);
- // delete trivial splits (with zero or almost zero areas)
- if (area1 == 0 || (signs_change && Math.Abs(area1) < 2))
- {
- SafeDisposeOutPts(ref op1!);
- outrec.pts = op2;
- }
- else if (area2 == 0 || (signs_change && Math.Abs(area2) < 2))
- {
- SafeDisposeOutPts(ref op2!);
- outrec.pts = op1;
- }
- else
- {
- OutRec newOr = new OutRec() { idx = _outrecList.Count };
- _outrecList.Add(newOr);
- newOr.polypath = null;
- if (_using_polytree)
- {
- outrec.splits ??= new List<OutRec>();
- outrec.splits.Add(newOr);
- }
- if (Math.Abs(area1) >= Math.Abs(area2))
- {
- outrec.pts = op1;
- newOr.pts = op2;
- }
- else
- {
- outrec.pts = op2;
- newOr.pts = op1;
- }
- if ((area1 > 0) == (area2 > 0))
- newOr.owner = outrec.owner;
- else
- newOr.owner = outrec;
- UpdateOutrecOwner(newOr);
- CleanCollinear(newOr);
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void CleanCollinear(OutRec? outrec)
- {
- outrec = GetRealOutRec(outrec);
- if (outrec == null || outrec.isOpen ||
- outrec.frontEdge != null || !ValidateClosedPathEx(ref outrec.pts))
- return;
- OutPt startOp = outrec.pts!;
- OutPt? op2 = startOp;
- for (; ; )
- {
- if (op2!.joiner != null) return;
- // NB if preserveCollinear == true, then only remove 180 deg. spikes
- if ((InternalClipper.CrossProduct(op2.prev.pt, op2.pt, op2.next!.pt) == 0) &&
- ((op2.pt == op2.prev.pt) || (op2.pt == op2.next.pt) || !PreserveCollinear ||
- (InternalClipper.DotProduct(op2.prev.pt, op2.pt, op2.next.pt) < 0)))
- {
- if (op2 == outrec.pts)
- outrec.pts = op2.prev;
- op2 = DisposeOutPt(op2);
- if (!ValidateClosedPathEx(ref op2))
- {
- outrec.pts = null;
- return;
- }
- startOp = op2!;
- continue;
- }
- op2 = op2.next;
- if (op2 == startOp) break;
- }
- FixSelfIntersects(outrec);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DoSplitOp(OutRec outrec, OutPt splitOp)
- {
- // splitOp.prev -> splitOp &&
- // splitOp.next -> splitOp.next.next are intersecting
- OutPt prevOp = splitOp.prev;
- OutPt nextNextOp = splitOp.next!.next!;
- outrec.pts = prevOp;
- OutPt result = prevOp;
- InternalClipper.GetIntersectPoint(
- prevOp.pt, splitOp.pt, splitOp.next.pt, nextNextOp.pt, out PointD ipD);
- Point64 ip = new Point64(ipD);
- #if USINGZ
- if (_zCallback != null)
- _zCallback(prevOp.pt, splitOp.pt, splitOp.next.pt, nextNextOp.pt, ref ip);
- #endif
- double area1 = Area(prevOp);
- double absArea1 = Math.Abs(area1);
- if (absArea1 < 2)
- {
- SafeDisposeOutPts(ref splitOp);
- return;
- }
- // nb: area1 is the path's area *before* splitting, whereas area2 is
- // the area of the triangle containing splitOp & splitOp.next.
- // So the only way for these areas to have the same sign is if
- // the split triangle is larger than the path containing prevOp or
- // if there's more than one self=intersection.
- double area2 = AreaTriangle(ip, splitOp.pt, splitOp.next.pt);
- double absArea2 = Math.Abs(area2);
- // de-link splitOp and splitOp.next from the path
- // while inserting the intersection point
- if (ip == prevOp.pt || ip == nextNextOp.pt)
- {
- nextNextOp.prev = prevOp;
- prevOp.next = nextNextOp;
- }
- else
- {
- OutPt newOp2 = new OutPt(ip, prevOp.outrec) { prev = prevOp, next = nextNextOp };
- nextNextOp.prev = newOp2;
- prevOp.next = newOp2;
- }
- SafeDeleteOutPtJoiners(splitOp.next);
- SafeDeleteOutPtJoiners(splitOp);
- if (absArea2 > 1 &&
- (absArea2 > absArea1 ||
- ((area2 > 0) == (area1 > 0))))
- {
- OutRec newOutRec = new OutRec();
- newOutRec.idx = _outrecList.Count;
- _outrecList.Add(newOutRec);
- newOutRec.owner = prevOp.outrec.owner;
- newOutRec.polypath = null;
- splitOp.outrec = newOutRec;
- splitOp.next.outrec = newOutRec;
- OutPt newOp = new OutPt(ip, newOutRec) { prev = splitOp.next, next = splitOp };
- newOutRec.pts = newOp;
- splitOp.prev = newOp;
- splitOp.next.next = newOp;
- }
- //else { splitOp = null; splitOp.next = null; }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void FixSelfIntersects(OutRec outrec)
- {
- OutPt op2 = outrec.pts!;
- for (; ; )
- {
- // triangles can't self-intersect
- if (op2.prev == op2.next!.next) break;
- if (InternalClipper.SegsIntersect(op2.prev.pt,
- op2.pt, op2.next.pt, op2.next.next!.pt))
- {
- DoSplitOp(outrec, op2);
- if (outrec.pts == null) return;
- op2 = outrec.pts;
- continue;
- }
- else
- op2 = op2.next;
- if (op2 == outrec.pts) break;
- }
- }
- internal static bool BuildPath(OutPt op, bool reverse, bool isOpen, Path64 path)
- {
- if (op.next == op || (!isOpen && op.next == op.prev)) return false;
- path.Clear();
- Point64 lastPt;
- OutPt op2;
- if (reverse)
- {
- lastPt = op.pt;
- op2 = op.prev;
- }
- else
- {
- op = op.next!;
- lastPt = op.pt;
- op2 = op.next!;
- }
- path.Add(lastPt);
- while (op2 != op)
- {
- if (op2.pt != lastPt)
- {
- lastPt = op2.pt;
- path.Add(lastPt);
- }
- if (reverse)
- op2 = op2.prev;
- else
- op2 = op2.next!;
- }
- if (path.Count == 3 && IsVerySmallTriangle(op2)) return false;
- else return true;
- }
- protected bool BuildPaths(Paths64 solutionClosed, Paths64 solutionOpen)
- {
- solutionClosed.Clear();
- solutionOpen.Clear();
- solutionClosed.Capacity = _outrecList.Count;
- solutionOpen.Capacity = _outrecList.Count;
- foreach (OutRec outrec in _outrecList)
- {
- if (outrec.pts == null) continue;
- Path64 path = new Path64();
- if (outrec.isOpen)
- {
- if (BuildPath(outrec.pts!, ReverseSolution, true, path))
- solutionOpen.Add(path);
- }
- else
- {
- // closed paths should always return a Positive orientation
- // except when ReverseSolution == true
- if (BuildPath(outrec.pts!, ReverseSolution, false, path))
- solutionClosed.Add(path);
- }
- }
- return true;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool Path1InsidePath2(OutRec or1, OutRec or2)
- {
- PointInPolygonResult result;
- OutPt op = or1.pts!;
- do
- {
- result = InternalClipper.PointInPolygon(op.pt, or2.path);
- if (result != PointInPolygonResult.IsOn) break;
- op = op.next!;
- } while (op != or1.pts);
- if (result == PointInPolygonResult.IsOn)
- return Area(op) < Area(or2.pts!);
- return result == PointInPolygonResult.IsInside;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Rect64 GetBounds(Path64 path)
- {
- if (path.Count == 0) return new Rect64();
- Rect64 result = new Rect64(long.MaxValue, long.MaxValue, -long.MaxValue, -long.MaxValue);
- foreach (Point64 pt in path)
- {
- if (pt.X < result.left) result.left = pt.X;
- if (pt.X > result.right) result.right = pt.X;
- if (pt.Y < result.top) result.top = pt.Y;
- if (pt.Y > result.bottom) result.bottom = pt.Y;
- }
- return result;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool DeepCheckOwner(OutRec outrec, OutRec owner)
- {
- if (owner.bounds.IsEmpty())
- owner.bounds = GetBounds(owner.path);
- bool isInsideOwnerBounds = owner.bounds.Contains(outrec.bounds);
- // while looking for the correct owner, check the owner's
- // splits **before** checking the owner itself because
- // splits can occur internally, and checking the owner
- // first would miss the inner split's true ownership
- if (owner.splits != null)
- foreach (OutRec asplit in owner.splits!)
- {
- OutRec? split = GetRealOutRec(asplit);
- if (split == null || split.idx <= owner.idx || split == outrec) continue;
- if (split.splits != null && DeepCheckOwner(outrec, split)) return true;
- if (split.path.Count == 0)
- BuildPath(split.pts!, ReverseSolution, false, split.path);
- if (split.bounds.IsEmpty()) split.bounds = GetBounds(split.path);
- if (split.bounds.Contains(outrec.bounds) && Path1InsidePath2(outrec, split))
- {
- outrec.owner = split;
- return true;
- }
- }
- // only continue when not inside recursion
- if (owner != outrec.owner) return false;
- for (; ; )
- {
- if (isInsideOwnerBounds && Path1InsidePath2(outrec, outrec.owner!))
- return true;
- outrec.owner = outrec.owner!.owner;
- if (outrec.owner == null) return false;
- isInsideOwnerBounds = outrec.owner.bounds.Contains(outrec.bounds);
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected bool BuildTree(PolyPathNode polytree, Paths64 solutionOpen)
- {
- polytree.Clear();
- solutionOpen.Clear();
- solutionOpen.Capacity = _outrecList.Count;
- for (int i = 0; i < _outrecList.Count; i++)
- {
- OutRec outrec = _outrecList[i];
- if (outrec.pts == null) continue;
- if (outrec.isOpen)
- {
- Path64 open_path = new Path64();
- if (BuildPath(outrec.pts!, ReverseSolution, true, open_path))
- solutionOpen.Add(open_path);
- continue;
- }
- if (!BuildPath(outrec.pts!, ReverseSolution, false, outrec.path)) continue;
- if (outrec.bounds.IsEmpty()) outrec.bounds = GetBounds(outrec.path);
- outrec.owner = GetRealOutRec(outrec.owner);
- if (outrec.owner != null)
- DeepCheckOwner(outrec, outrec.owner);
- // swap order if outer/owner paths are preceeded by their inner paths
- if (outrec.owner != null && outrec.owner.idx > outrec.idx)
- {
- int j = outrec.owner.idx;
- outrec.owner.idx = i;
- outrec.idx = j;
- _outrecList[i] = _outrecList[j];
- _outrecList[j] = outrec;
- outrec = _outrecList[i];
- outrec.owner = GetRealOutRec(outrec.owner);
- BuildPath(outrec.pts!, ReverseSolution, false, outrec.path);
- if (outrec.bounds.IsEmpty()) outrec.bounds = GetBounds(outrec.path);
- if (outrec.owner != null)
- DeepCheckOwner(outrec, outrec.owner);
- }
- PolyPathNode ownerPP;
- if (outrec.owner != null && outrec.owner.polypath != null)
- ownerPP = outrec.owner.polypath;
- else
- ownerPP = polytree;
- outrec.polypath = ownerPP.AddChild(outrec.path);
- }
- return true;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Rect64 GetBounds()
- {
- Rect64 bounds = Clipper.MaxInvalidRect64;
- foreach (Vertex t in _vertexList)
- {
- Vertex v = t;
- do
- {
- if (v.pt.X < bounds.left) bounds.left = v.pt.X;
- if (v.pt.X > bounds.right) bounds.right = v.pt.X;
- if (v.pt.Y < bounds.top) bounds.top = v.pt.Y;
- if (v.pt.Y > bounds.bottom) bounds.bottom = v.pt.Y;
- v = v.next!;
- } while (v != t);
- }
- return bounds.IsEmpty() ? new Rect64(0, 0, 0, 0) : bounds;
- }
- } // ClipperBase class
- public class Clipper64 : ClipperBase
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal new void AddPath(Path64 path, PathType polytype, bool isOpen = false)
- {
- base.AddPath(path, polytype, isOpen);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal new void AddPaths(Paths64 paths, PathType polytype, bool isOpen = false)
- {
- base.AddPaths(paths, polytype, isOpen);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddSubject(Paths64 paths)
- {
- AddPaths(paths, PathType.Subject);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddOpenSubject(Paths64 paths)
- {
- AddPaths(paths, PathType.Subject, true);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddClip(Paths64 paths)
- {
- AddPaths(paths, PathType.Clip);
- }
- public bool Execute(ClipType clipType, FillRule fillRule,
- Paths64 solutionClosed, Paths64 solutionOpen)
- {
- solutionClosed.Clear();
- solutionOpen.Clear();
- try
- {
- ExecuteInternal(clipType, fillRule);
- BuildPaths(solutionClosed, solutionOpen);
- }
- catch
- {
- _succeeded = false;
- }
- ClearSolution();
- return _succeeded;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool Execute(ClipType clipType, FillRule fillRule, Paths64 solutionClosed)
- {
- return Execute(clipType, fillRule, solutionClosed, new Paths64());
- }
- public bool Execute(ClipType clipType, FillRule fillRule, PolyTree64 polytree, Paths64 openPaths)
- {
- polytree.Clear();
- openPaths.Clear();
- _using_polytree = true;
- try
- {
- ExecuteInternal(clipType, fillRule);
- BuildTree(polytree, openPaths);
- }
- catch
- {
- _succeeded = false;
- }
- ClearSolution();
- return _succeeded;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool Execute(ClipType clipType, FillRule fillRule, PolyTree64 polytree)
- {
- return Execute(clipType, fillRule, polytree, new Paths64());
- }
- #if USINGZ
- public ZCallback64? ZCallback {
- get { return this._zCallback; }
- set { this._zCallback = value; }
- }
- #endif
- } // Clipper64 class
- public class ClipperD : ClipperBase
- {
- private static string precision_range_error = "Error: Precision is out of range.";
- private readonly double _scale;
- private readonly double _invScale;
- #if USINGZ
- public delegate void ZCallbackD(PointD bot1, PointD top1,
- PointD bot2, PointD top2, ref PointD intersectPt);
- public ZCallbackD? ZCallback { get; set; }
- private void CheckZCallback()
- {
- if (ZCallback != null)
- _zCallback = ZCB;
- else
- _zCallback = null;
- }
- #endif
- public ClipperD(int roundingDecimalPrecision = 2)
- {
- if (roundingDecimalPrecision < -8 || roundingDecimalPrecision > 8)
- throw new ClipperLibException(precision_range_error);
- _scale = Math.Pow(10, roundingDecimalPrecision);
- _invScale = 1 / _scale;
- }
- #if USINGZ
- private void ZCB(Point64 bot1, Point64 top1,
- Point64 bot2, Point64 top2, ref Point64 intersectPt)
- {
- // de-scale (x & y)
- // temporarily convert integers to their initial float values
- // this will slow clipping marginally but will make it much easier
- // to understand the coordinates passed to the callback function
- PointD tmp = new PointD(intersectPt);
- //do the callback
- ZCallback?.Invoke(
- Clipper.ScalePointD(bot1, _invScale),
- Clipper.ScalePointD(top1, _invScale),
- Clipper.ScalePointD(bot2, _invScale),
- Clipper.ScalePointD(top2, _invScale), ref tmp);
- intersectPt = new Point64(intersectPt.X,
- intersectPt.Y, tmp.z);
- }
- #endif
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddPath(PathD path, PathType polytype, bool isOpen = false)
- {
- base.AddPath(Clipper.ScalePath64(path, _scale), polytype, isOpen);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddPaths(PathsD paths, PathType polytype, bool isOpen = false)
- {
- base.AddPaths(Clipper.ScalePaths64(paths, _scale), polytype, isOpen);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddSubject(PathD path)
- {
- AddPath(path, PathType.Subject);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddOpenSubject(PathD path)
- {
- AddPath(path, PathType.Subject, true);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddClip(PathD path)
- {
- AddPath(path, PathType.Clip);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddSubject(PathsD paths)
- {
- AddPaths(paths, PathType.Subject);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddOpenSubject(PathsD paths)
- {
- AddPaths(paths, PathType.Subject, true);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddClip(PathsD paths)
- {
- AddPaths(paths, PathType.Clip);
- }
- public bool Execute(ClipType clipType, FillRule fillRule,
- PathsD solutionClosed, PathsD solutionOpen)
- {
- Paths64 solClosed64 = new Paths64(), solOpen64 = new Paths64();
- #if USINGZ
- CheckZCallback();
- #endif
- bool success = true;
- solutionClosed.Clear();
- solutionOpen.Clear();
- try
- {
- ExecuteInternal(clipType, fillRule);
- BuildPaths(solClosed64, solOpen64);
- }
- catch
- {
- success = false;
- }
- ClearSolution();
- if (!success) return false;
- solutionClosed.Capacity = solClosed64.Count;
- foreach (Path64 path in solClosed64)
- solutionClosed.Add(Clipper.ScalePathD(path, _invScale));
- solutionOpen.Capacity = solOpen64.Count;
- foreach (Path64 path in solOpen64)
- solutionOpen.Add(Clipper.ScalePathD(path, _invScale));
- return true;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool Execute(ClipType clipType, FillRule fillRule, PathsD solutionClosed)
- {
- return Execute(clipType, fillRule, solutionClosed, new PathsD());
- }
- public bool Execute(ClipType clipType, FillRule fillRule, PolyTreeD polytree, PathsD openPaths)
- {
- polytree.Clear();
- (polytree as PolyPathD).Scale = _scale;
- #if USINGZ
- CheckZCallback();
- #endif
- openPaths.Clear();
- Paths64 oPaths = new Paths64();
- bool success = true;
- try
- {
- ExecuteInternal(clipType, fillRule);
- BuildTree(polytree, oPaths);
- }
- catch
- {
- success = false;
- }
- ClearSolution();
- if (!success) return false;
- if (oPaths.Count > 0)
- {
- openPaths.Capacity = oPaths.Count;
- foreach (Path64 path in oPaths)
- openPaths.Add(Clipper.ScalePathD(path, _invScale));
- }
- return true;
- }
- public bool Execute(ClipType clipType, FillRule fillRule, PolyTreeD polytree)
- {
- return Execute(clipType, fillRule, polytree, new PathsD());
- }
- } // ClipperD class
- public abstract class PolyPathNode : IEnumerable
- {
- internal PolyPathNode? _parent;
- internal List<PolyPathNode> _childs = new List<PolyPathNode>();
- public IEnumerator GetEnumerator()
- {
- return new NodeEnumerator(_childs);
- }
- private class NodeEnumerator : IEnumerator
- {
- private int position = -1;
- private readonly List<PolyPathNode> _nodes;
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public NodeEnumerator(List<PolyPathNode> nodes)
- {
- _nodes = new List<PolyPathNode>(nodes);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private IEnumerator getEnumerator()
- {
- return (IEnumerator)this;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool MoveNext()
- {
- position++;
- return (position < _nodes.Count);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Reset()
- {
- position = -1;
- }
- public object Current
- {
- get
- {
- if (position < 0 || position >= _nodes.Count)
- throw new InvalidOperationException();
- return _nodes[position];
- }
- }
- };
- public bool IsHole => GetIsHole();
- public PolyPathNode(PolyPathNode? parent = null) { _parent = parent; }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool GetIsHole()
- {
- bool result = true;
- PolyPathNode? pp = _parent;
- while (pp != null)
- {
- result = !result;
- pp = pp._parent;
- }
- return result;
- }
- public int Count => _childs.Count;
- internal abstract PolyPathNode AddChild(Path64 p);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Clear()
- {
- _childs.Clear();
- }
- } // PolyPathBase class
- public class PolyPath64 : PolyPathNode
- {
- public Path64? Polygon { get; private set; } // polytree root's polygon == null
- public PolyPath64(PolyPathNode? parent = null) : base(parent) { }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal override PolyPathNode AddChild(Path64 p)
- {
- PolyPathNode newChild = new PolyPath64(this);
- (newChild as PolyPath64)!.Polygon = p;
- _childs.Add(newChild);
- return newChild;
- }
- [IndexerName("Child")]
- public PolyPath64 this[int index]
- {
- get
- {
- if (index < 0 || index >= _childs.Count)
- throw new InvalidOperationException();
- return (PolyPath64)_childs[index];
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public double Area()
- {
- double result = Polygon == null ? 0 : Clipper.Area(Polygon);
- foreach (PolyPathNode polyPathBase in _childs)
- {
- PolyPath64 child = (PolyPath64)polyPathBase;
- result += child.Area();
- }
- return result;
- }
- }
- public class PolyPathD : PolyPathNode
- {
- internal double Scale { get; set; }
- public PathD? Polygon { get; private set; }
- public PolyPathD(PolyPathNode? parent = null) : base(parent) { }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal override PolyPathNode AddChild(Path64 p)
- {
- PolyPathNode newChild = new PolyPathD(this);
- (newChild as PolyPathD)!.Scale = Scale;
- (newChild as PolyPathD)!.Polygon = Clipper.ScalePathD(p, 1 / Scale);
- _childs.Add(newChild);
- return newChild;
- }
- [IndexerName("Child")]
- public PolyPathD this[int index]
- {
- get
- {
- if (index < 0 || index >= _childs.Count)
- throw new InvalidOperationException();
- return (PolyPathD)_childs[index];
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public double Area()
- {
- double result = Polygon == null ? 0 : Clipper.Area(Polygon);
- foreach (PolyPathNode polyPathBase in _childs)
- {
- PolyPathD child = (PolyPathD)polyPathBase;
- result += child.Area();
- }
- return result;
- }
- }
- public class PolyTree64 : PolyPath64 { }
- public class PolyTreeD : PolyPathD
- {
- public new double Scale => base.Scale;
- }
- public class ClipperLibException : Exception
- {
- public ClipperLibException(string description) : base(description) { }
- }
- } // namespace
|