123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762 |
-
- using System;
- using System.Collections.Generic;
- using System.Linq;
- namespace Clipper2Lib
- {
- public class RectClip
- {
- protected enum Location
- {
- left, top, right, bottom, inside
- };
- readonly protected Rect64 rect_;
- readonly protected Point64 mp_;
- readonly protected Path64 rectPath_;
- protected Path64 result_;
- protected Location firstCross_;
- protected List<Location> startLocs_ = new List<Location>();
- internal RectClip(Rect64 rect)
- {
- rect_ = rect;
- mp_ = rect.MidPoint();
- rectPath_ = rect_.AsPath();
- result_ = new Path64();
- firstCross_ = Location.inside;
- }
- private static PointInPolygonResult Path1ContainsPath2(Path64 path1, Path64 path2)
- {
- PointInPolygonResult result = PointInPolygonResult.IsOn;
- foreach (Point64 pt in path2)
- {
- result = Clipper.PointInPolygon(pt, path1);
- if (result != PointInPolygonResult.IsOn)
- {
- break;
- }
- }
- return result;
- }
- private static bool IsClockwise(Location prev, Location curr,
- Point64 prevPt, Point64 currPt, Point64 rectMidPoint)
- {
- if (AreOpposites(prev, curr))
- {
- return InternalClipper.CrossProduct(prevPt, rectMidPoint, currPt) < 0;
- }
- else
- {
- return HeadingClockwise(prev, curr);
- }
- }
- private static bool AreOpposites(Location prev, Location curr)
- {
- return Math.Abs((int)prev - (int)curr) == 2;
- }
- private static bool HeadingClockwise(Location prev, Location curr)
- {
- return ((int)prev + 1) % 4 == (int)curr;
- }
- private static Location GetAdjacentLocation(Location loc, bool isClockwise)
- {
- int delta = (isClockwise) ? 1 : 3;
- return (Location)(((int)loc + delta) % 4);
- }
- private void AddCorner(Location prev, Location curr)
- {
- if (HeadingClockwise(prev, curr))
- {
- result_.Add(rectPath_[(int)prev]);
- }
- else
- {
- result_.Add(rectPath_[(int)curr]);
- }
- }
- private void AddCorner(ref Location loc, bool isClockwise)
- {
- if (isClockwise)
- {
- result_.Add(rectPath_[(int)loc]);
- loc = GetAdjacentLocation(loc, true);
- }
- else
- {
- loc = GetAdjacentLocation(loc, false);
- result_.Add(rectPath_[(int)loc]);
- }
- }
- static protected bool GetLocation(Rect64 rec, Point64 pt, out Location loc)
- {
- if (pt.X == rec.left && pt.Y >= rec.top && pt.Y <= rec.bottom)
- {
- loc = Location.left; return false; // pt on rec
- }
- if (pt.X == rec.right && pt.Y >= rec.top && pt.Y <= rec.bottom)
- {
- loc = Location.right; return false; // pt on rec
- }
- if (pt.Y == rec.top && pt.X >= rec.left && pt.X <= rec.right)
- {
- loc = Location.top; return false; // pt on rec
- }
- if (pt.Y == rec.bottom && pt.X >= rec.left && pt.X <= rec.right)
- {
- loc = Location.bottom; return false; // pt on rec
- }
- if (pt.X < rec.left)
- {
- loc = Location.left;
- }
- else if (pt.X > rec.right)
- {
- loc = Location.right;
- }
- else if (pt.Y < rec.top)
- {
- loc = Location.top;
- }
- else if (pt.Y > rec.bottom)
- {
- loc = Location.bottom;
- }
- else
- {
- loc = Location.inside;
- }
- return true;
- }
- static protected bool GetIntersection(Path64 rectPath, Point64 p, Point64 p2, ref Location loc, out Point64 ip)
- {
- // gets the pt of intersection between rectPath and segment(p, p2) that's closest to 'p'
- // when result == false, loc will remain unchanged
- ip = new Point64();
- switch (loc)
- {
- case Location.left:
- if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip);
- }
- else if (p.Y < rectPath[0].Y &&
- InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip);
- loc = Location.top;
- }
- else if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip);
- loc = Location.bottom;
- }
- else
- {
- return false;
- }
- break;
- case Location.right:
- if (InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip);
- }
- else if (p.Y < rectPath[0].Y &&
- InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip);
- loc = Location.top;
- }
- else if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip);
- loc = Location.bottom;
- }
- else
- {
- return false;
- }
- break;
- case Location.top:
- if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip);
- }
- else if (p.X < rectPath[0].X &&
- InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip);
- loc = Location.left;
- }
- else if (p.X > rectPath[1].X &&
- InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip);
- loc = Location.right;
- }
- else
- {
- return false;
- }
- break;
- case Location.bottom:
- if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip);
- }
- else if (p.X < rectPath[3].X &&
- InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip);
- loc = Location.left;
- }
- else if (p.X > rectPath[2].X &&
- InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip);
- loc = Location.right;
- }
- else
- {
- return false;
- }
- break;
- case Location.inside:
- if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip);
- loc = Location.left;
- }
- else if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip);
- loc = Location.top;
- }
- else if (InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip);
- loc = Location.right;
- }
- else if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true))
- {
- InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip);
- loc = Location.bottom;
- }
- else
- {
- return false;
- }
- break;
- }
- return true;
- }
- protected void GetNextLocation(Path64 path,
- ref Location loc, ref int i, int highI)
- {
- switch (loc)
- {
- case Location.left:
- {
- while (i <= highI && path[i].X <= rect_.left)
- {
- i++;
- }
- if (i > highI)
- {
- break;
- }
- if (path[i].X >= rect_.right)
- {
- loc = Location.right;
- }
- else if (path[i].Y <= rect_.top)
- {
- loc = Location.top;
- }
- else if (path[i].Y >= rect_.bottom)
- {
- loc = Location.bottom;
- }
- else
- {
- loc = Location.inside;
- }
- }
- break;
- case Location.top:
- {
- while (i <= highI && path[i].Y <= rect_.top)
- {
- i++;
- }
- if (i > highI)
- {
- break;
- }
- if (path[i].Y >= rect_.bottom)
- {
- loc = Location.bottom;
- }
- else if (path[i].X <= rect_.left)
- {
- loc = Location.left;
- }
- else if (path[i].X >= rect_.right)
- {
- loc = Location.right;
- }
- else
- {
- loc = Location.inside;
- }
- }
- break;
- case Location.right:
- {
- while (i <= highI && path[i].X >= rect_.right)
- {
- i++;
- }
- if (i > highI)
- {
- break;
- }
- if (path[i].X <= rect_.left)
- {
- loc = Location.left;
- }
- else if (path[i].Y <= rect_.top)
- {
- loc = Location.top;
- }
- else if (path[i].Y >= rect_.bottom)
- {
- loc = Location.bottom;
- }
- else
- {
- loc = Location.inside;
- }
- }
- break;
- case Location.bottom:
- {
- while (i <= highI && path[i].Y >= rect_.bottom)
- {
- i++;
- }
- if (i > highI)
- {
- break;
- }
- if (path[i].Y <= rect_.top)
- {
- loc = Location.top;
- }
- else if (path[i].X <= rect_.left)
- {
- loc = Location.left;
- }
- else if (path[i].X >= rect_.right)
- {
- loc = Location.right;
- }
- else
- {
- loc = Location.inside;
- }
- }
- break;
- case Location.inside:
- {
- while (i <= highI)
- {
- if (path[i].X < rect_.left)
- {
- loc = Location.left;
- }
- else if (path[i].X > rect_.right)
- {
- loc = Location.right;
- }
- else if (path[i].Y > rect_.bottom)
- {
- loc = Location.bottom;
- }
- else if (path[i].Y < rect_.top)
- {
- loc = Location.top;
- }
- else
- {
- result_.Add(path[i]);
- i++;
- continue;
- }
- break;
- }
- }
- break;
- } // switch
- }
- internal Path64 ExecuteInternal(Path64 path)
- {
- if (path.Count < 3 || rect_.IsEmpty())
- {
- return new Path64();
- }
- result_.Clear();
- startLocs_.Clear();
- int i = 0, highI = path.Count - 1;
- firstCross_ = Location.inside;
- Location crossingLoc = Location.inside, prev;
- if (!GetLocation(rect_, path[highI], out Location loc))
- {
- prev = loc;
- i = highI - 1;
- while (i >= 0 && !GetLocation(rect_, path[i], out prev))
- {
- i--;
- }
- if (i < 0)
- {
- return path;
- }
- if (prev == Location.inside)
- {
- loc = Location.inside;
- }
- i = 0;
- }
- Location startingLoc = loc;
- ///////////////////////////////////////////////////
- while (i <= highI)
- {
- prev = loc;
- Location prevCrossLoc = crossingLoc;
- GetNextLocation(path, ref loc, ref i, highI);
- if (i > highI)
- {
- break;
- }
- Point64 prevPt = (i == 0) ? path[highI] : path[i - 1];
- crossingLoc = loc;
- if (!GetIntersection(rectPath_, path[i], prevPt, ref crossingLoc, out Point64 ip))
- {
- // ie remaining outside (& crossingLoc still == loc)
- if (prevCrossLoc == Location.inside)
- {
- bool isClockw = IsClockwise(prev, loc, prevPt, path[i], mp_);
- do
- {
- startLocs_.Add(prev);
- prev = GetAdjacentLocation(prev, isClockw);
- } while (prev != loc);
- crossingLoc = prevCrossLoc; // still not crossed
- }
- else if (prev != Location.inside && prev != loc)
- {
- bool isClockw = IsClockwise(prev, loc, prevPt, path[i], mp_);
- do
- {
- AddCorner(ref prev, isClockw);
- } while (prev != loc);
- }
- ++i;
- continue;
- }
- ////////////////////////////////////////////////////
- // we must be crossing the rect boundary to get here
- ////////////////////////////////////////////////////
- if (loc == Location.inside) // path must be entering rect
- {
- if (firstCross_ == Location.inside)
- {
- firstCross_ = crossingLoc;
- startLocs_.Add(prev);
- }
- else if (prev != crossingLoc)
- {
- bool isClockw = IsClockwise(prev, crossingLoc, prevPt, path[i], mp_);
- do
- {
- AddCorner(ref prev, isClockw);
- } while (prev != crossingLoc);
- }
- }
- else if (prev != Location.inside)
- {
- // passing right through rect. 'ip' here will be the second
- // intersect pt but we'll also need the first intersect pt (ip2)
- loc = prev;
- GetIntersection(rectPath_, prevPt, path[i], ref loc, out Point64 ip2);
- if (prevCrossLoc != Location.inside)
- {
- AddCorner(prevCrossLoc, loc);
- }
- if (firstCross_ == Location.inside)
- {
- firstCross_ = loc;
- startLocs_.Add(prev);
- }
- loc = crossingLoc;
- result_.Add(ip2);
- if (ip == ip2)
- {
- // it's very likely that path[i] is on rect
- GetLocation(rect_, path[i], out loc);
- AddCorner(crossingLoc, loc);
- crossingLoc = loc;
- continue;
- }
- }
- else // path must be exiting rect
- {
- loc = crossingLoc;
- if (firstCross_ == Location.inside)
- {
- firstCross_ = crossingLoc;
- }
- }
- result_.Add(ip);
- } //while i <= highI
- ///////////////////////////////////////////////////
- // path must be entering rect
- if (firstCross_ == Location.inside)
- {
- if (startingLoc == Location.inside)
- {
- return path;
- }
- Rect64 tmp_rect = Clipper.GetBounds(path);
- if (tmp_rect.Contains(rect_) &&
- Path1ContainsPath2(path, rectPath_)
- != PointInPolygonResult.IsOutside)
- {
- return rectPath_;
- }
- return new Path64();
- }
- if (loc != Location.inside &&
- (loc != firstCross_ || startLocs_.Count > 2))
- {
- if (startLocs_.Count > 0)
- {
- prev = loc;
- foreach (Location loc2 in startLocs_)
- {
- if (prev == loc2)
- {
- continue;
- }
- AddCorner(ref prev, HeadingClockwise(prev, loc2));
- prev = loc2;
- }
- loc = prev;
- }
- if (loc != firstCross_)
- {
- AddCorner(ref loc, HeadingClockwise(loc, firstCross_));
- }
- }
- if (result_.Count < 3)
- {
- return new Path64();
- }
- // finally, tidy up result
- int k = 0, len = result_.Count;
- Point64 lastPt = result_[len - 1];
- Path64 result = new Path64(len) { result_[0] };
- foreach (Point64 pt in result_.Skip(1))
- {
- if (InternalClipper.CrossProduct(lastPt, result[k], pt) != 0)
- {
- lastPt = result[k++];
- result.Add(pt);
- }
- else
- {
- result[k] = pt;
- }
- }
- if (k < 2)
- {
- result.Clear();
- }
- else if (InternalClipper.CrossProduct(result[0], result[k - 1], result[k]) == 0)
- {
- result.RemoveAt(result.Count - 1);
- }
- return result;
- }
- internal Paths64 ExecuteInternal(Paths64 paths)
- {
- Paths64 result = new Paths64(paths.Count);
- foreach (Path64 path in paths)
- {
- if (rect_.Intersects(Clipper.GetBounds(path)))
- {
- result.Add(ExecuteInternal(path));
- }
- }
- return result;
- }
- } // RectClip class
- public class RectClipLines : RectClip
- {
- internal RectClipLines(Rect64 rect) : base(rect) { }
- internal new Paths64 ExecuteInternal(Path64 path)
- {
- result_.Clear();
- Paths64 result = new Paths64();
- if (path.Count < 2 || rect_.IsEmpty())
- {
- return result;
- }
- Location prev = Location.inside;
- int i = 1, highI = path.Count - 1;
- if (!GetLocation(rect_, path[0], out Location loc))
- {
- while (i <= highI && !GetLocation(rect_, path[i], out prev))
- {
- i++;
- }
- if (i > highI)
- {
- result.Add(path);
- return result;
- }
- if (prev == Location.inside)
- {
- loc = Location.inside;
- }
- i = 1;
- }
- if (loc == Location.inside)
- {
- result_.Add(path[0]);
- }
- ///////////////////////////////////////////////////
- while (i <= highI)
- {
- prev = loc;
- GetNextLocation(path, ref loc, ref i, highI);
- if (i > highI)
- {
- break;
- }
- Point64 prevPt = path[i - 1];
- Location crossingLoc = loc;
- if (!GetIntersection(rectPath_, path[i], prevPt, ref crossingLoc, out Point64 ip))
- {
- // ie remaining outside (& crossingLoc still == loc)
- ++i;
- continue;
- }
- ////////////////////////////////////////////////////
- // we must be crossing the rect boundary to get here
- ////////////////////////////////////////////////////
- if (loc == Location.inside) // path must be entering rect
- {
- result_.Add(ip);
- }
- else if (prev != Location.inside)
- {
- // passing right through rect. 'ip' here will be the second
- // intersect pt but we'll also need the first intersect pt (ip2)
- crossingLoc = prev;
- GetIntersection(rectPath_, prevPt, path[i], ref crossingLoc, out Point64 ip2);
- result_.Add(ip2);
- result_.Add(ip);
- result.Add(result_);
- result_ = new Path64();
- }
- else // path must be exiting rect
- {
- result_.Add(ip);
- result.Add(result_);
- result_ = new Path64();
- }
- } //while i <= highI
- ///////////////////////////////////////////////////
- if (result_.Count > 1)
- {
- result.Add(result_);
- result_ = new Path64();
- }
- return result;
- } // RectClipOpen.ExecuteInternal
- } // RectClipOpen class
- } // namespace
|