You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1068 lines
30 KiB
C#

/*******************************************************************************
* Author : Angus Johnson *
* Date : 10 October 2024 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : FAST rectangular clipping *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Clipper2Lib
{
public class OutPt2
{
public OutPt2? next;
public OutPt2? prev;
public Point64 pt;
public int ownerIdx;
public List<OutPt2?>? edge;
public OutPt2(Point64 pt)
{
this.pt = pt;
}
}
public class RectClip64
{
protected enum Location
{
left, top, right, bottom, inside
}
readonly protected Rect64 rect_;
readonly protected Point64 mp_;
readonly protected Path64 rectPath_;
protected Rect64 pathBounds_;
protected List<OutPt2?> results_;
protected List<OutPt2?>[] edges_;
protected int currIdx_;
internal RectClip64(Rect64 rect)
{
currIdx_ = -1;
rect_ = rect;
mp_ = rect.MidPoint();
rectPath_ = rect_.AsPath();
results_ = new List<OutPt2?>();
edges_ = new List<OutPt2?>[8];
for (int i = 0; i < 8; i++)
edges_[i] = new List<OutPt2?>();
}
internal OutPt2 Add(Point64 pt, bool startingNewPath = false)
{ // this method is only called by InternalExecute.
// Later splitting and rejoining won't create additional op's,
// though they will change the (non-storage) fResults count.
int currIdx = results_.Count;
OutPt2 result;
if ((currIdx == 0) || startingNewPath)
{
result = new OutPt2(pt);
results_.Add(result);
result.ownerIdx = currIdx;
result.prev = result;
result.next = result;
}
else
{
currIdx--;
OutPt2? prevOp = results_[currIdx];
if (prevOp!.pt == pt) return prevOp;
result = new OutPt2(pt)
{
ownerIdx = currIdx,
next = prevOp.next
};
prevOp.next!.prev = result;
prevOp.next = result;
result.prev = prevOp;
results_[currIdx] = result;
}
return result;
}
private static bool Path1ContainsPath2(Path64 path1, Path64 path2)
{
// nb: occasionally, due to rounding, path1 may
// appear (momentarily) inside or outside path2.
int ioCount = 0;
foreach (Point64 pt in path2)
{
PointInPolygonResult pip =
InternalClipper.PointInPolygon(pt, path1);
switch(pip)
{
case PointInPolygonResult.IsInside:
ioCount--; break;
case PointInPolygonResult.IsOutside:
ioCount++; break;
}
if (Math.Abs(ioCount) > 1) break;
}
return ioCount <= 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
return HeadingClockwise(prev, curr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool AreOpposites(Location prev, Location curr)
{
return Math.Abs((int)prev - (int) curr) == 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool HeadingClockwise(Location prev, Location curr)
{
return ((int) prev + 1) % 4 == (int) curr;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Location GetAdjacentLocation(Location loc, bool isClockwise)
{
int delta = (isClockwise) ? 1 : 3;
return (Location)(((int) loc + delta) % 4);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static OutPt2? UnlinkOp(OutPt2 op)
{
if (op.next == op) return null;
op.prev!.next = op.next;
op.next!.prev = op.prev;
return op.next;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static OutPt2? UnlinkOpBack(OutPt2 op)
{
if (op.next == op) return null;
op.prev!.next = op.next;
op.next!.prev = op.prev;
return op.prev;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint GetEdgesForPt(Point64 pt, Rect64 rec)
{
uint result = 0;
if (pt.X == rec.left) result = 1;
else if (pt.X == rec.right) result = 4;
if (pt.Y == rec.top) result += 2;
else if (pt.Y == rec.bottom) result += 8;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsHeadingClockwise(Point64 pt1, Point64 pt2, int edgeIdx)
{
return edgeIdx switch
{
0 => pt2.Y < pt1.Y,
1 => pt2.X > pt1.X,
2 => pt2.Y > pt1.Y,
_ => pt2.X < pt1.X
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool HasHorzOverlap(Point64 left1, Point64 right1,
Point64 left2, Point64 right2)
{
return (left1.X < right2.X) && (right1.X > left2.X);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool HasVertOverlap(Point64 top1, Point64 bottom1,
Point64 top2, Point64 bottom2)
{
return (top1.Y < bottom2.Y) && (bottom1.Y > top2.Y);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void AddToEdge(List<OutPt2?> edge, OutPt2 op)
{
if (op.edge != null) return;
op.edge = edge;
edge.Add(op);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void UncoupleEdge(OutPt2 op)
{
if (op.edge == null) return;
for (int i = 0; i < op.edge.Count; i++)
{
OutPt2? op2 = op.edge[i];
if (op2 != op) continue;
op.edge[i] = null;
break;
}
op.edge = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SetNewOwner(OutPt2 op, int newIdx)
{
op.ownerIdx = newIdx;
OutPt2 op2 = op.next!;
while (op2 != op)
{
op2.ownerIdx = newIdx;
op2 = op2.next!;
}
}
private void AddCorner(Location prev, Location curr)
{
Add(HeadingClockwise(prev, curr) ? rectPath_[(int) prev] : rectPath_[(int) curr]);
}
private void AddCorner(ref Location loc, bool isClockwise)
{
if (isClockwise)
{
Add(rectPath_[(int) loc]);
loc = GetAdjacentLocation(loc, true);
}
else
{
loc = GetAdjacentLocation(loc, false);
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;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsHorizontal(Point64 pt1, Point64 pt2)
{
return pt1.Y == pt2.Y;
}
private static bool GetSegmentIntersection(Point64 p1,
Point64 p2, Point64 p3, Point64 p4, out Point64 ip)
{
double res1 = InternalClipper.CrossProduct(p1, p3, p4);
double res2 = InternalClipper.CrossProduct(p2, p3, p4);
if (res1 == 0)
{
ip = p1;
if (res2 == 0) return false; // segments are collinear
if (p1 == p3 || p1 == p4) return true;
//else if (p2 == p3 || p2 == p4) { ip = p2; return true; }
if (IsHorizontal(p3, p4)) return ((p1.X > p3.X) == (p1.X < p4.X));
return ((p1.Y > p3.Y) == (p1.Y < p4.Y));
}
if (res2 == 0)
{
ip = p2;
if (p2 == p3 || p2 == p4) return true;
if (IsHorizontal(p3, p4)) return ((p2.X > p3.X) == (p2.X < p4.X));
return ((p2.Y > p3.Y) == (p2.Y < p4.Y));
}
if ((res1 > 0) == (res2 > 0))
{
ip = new Point64(0, 0);
return false;
}
double res3 = InternalClipper.CrossProduct(p3, p1, p2);
double res4 = InternalClipper.CrossProduct(p4, p1, p2);
if (res3 == 0)
{
ip = p3;
if (p3 == p1 || p3 == p2) return true;
if (IsHorizontal(p1, p2)) return ((p3.X > p1.X) == (p3.X < p2.X));
return ((p3.Y > p1.Y) == (p3.Y < p2.Y));
}
if (res4 == 0)
{
ip = p4;
if (p4 == p1 || p4 == p2) return true;
if (IsHorizontal(p1, p2)) return ((p4.X > p1.X) == (p4.X < p2.X));
return ((p4.Y > p1.Y) == (p4.Y < p2.Y));
}
if ((res3 > 0) == (res4 > 0))
{
ip = new Point64(0, 0);
return false;
}
// segments must intersect to get here
return InternalClipper.GetSegmentIntersectPt(p1, p2, p3, p4, out ip);
}
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 (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip))
return true;
if (p.Y < rectPath[0].Y && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip))
{
loc = Location.top;
return true;
}
if (!GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) return false;
loc = Location.bottom;
return true;
case Location.right:
if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip))
return true;
if (p.Y < rectPath[0].Y && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip))
{
loc = Location.top;
return true;
}
if (!GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) return false;
loc = Location.bottom;
return true;
case Location.top:
if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip))
return true;
if (p.X < rectPath[0].X && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip))
{
loc = Location.left;
return true;
}
if (p.X <= rectPath[1].X || !GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) return false;
loc = Location.right;
return true;
case Location.bottom:
if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip))
return true;
if (p.X < rectPath[3].X && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip))
{
loc = Location.left;
return true;
}
if (p.X <= rectPath[2].X || !GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) return false;
loc = Location.right;
return true;
default:
if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip))
{
loc = Location.left;
return true;
}
if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip))
{
loc = Location.top;
return true;
}
if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip))
{
loc = Location.right;
return true;
}
if (!GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) return false;
loc = Location.bottom;
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
{
Add(path[i]);
i++;
continue;
}
break;
}
}
break;
} // switch
}
private static bool StartLocsAreClockwise(List<Location> startLocs)
{
int result = 0;
for (int i = 1; i < startLocs.Count; i++)
{
int d = (int)startLocs[i] - (int)startLocs[i - 1];
switch (d)
{
case -1: result -= 1; break;
case 1: result += 1; break;
case -3: result += 1; break;
case 3: result -= 1; break;
}
}
return result > 0;
}
private void ExecuteInternal(Path64 path)
{
if (path.Count < 3 || rect_.IsEmpty()) return;
List<Location> startLocs = new List<Location>();
Location firstCross = Location.inside;
Location crossingLoc = firstCross, prev = firstCross;
int i, highI = path.Count - 1;
if (!GetLocation(rect_, path[highI], out Location loc))
{
i = highI - 1;
while (i >= 0 && !GetLocation(rect_, path[i], out prev)) i--;
if (i < 0)
{
foreach(Point64 pt in path) { Add(pt); }
return;
}
if (prev == Location.inside) loc = Location.inside;
}
Location startingLoc = loc;
///////////////////////////////////////////////////
i = 0;
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
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 && prevCrossLoc != loc) //#597
AddCorner(prevCrossLoc, loc);
if (firstCross == Location.inside)
{
firstCross = loc;
startLocs.Add(prev);
}
loc = crossingLoc;
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;
}
Add(ip);
} //while i <= highI
///////////////////////////////////////////////////
if (firstCross == Location.inside)
{
// path never intersects
if (startingLoc == Location.inside) return;
if (!pathBounds_.Contains(rect_) ||
!Path1ContainsPath2(path, rectPath_)) return;
bool startLocsClockwise = StartLocsAreClockwise(startLocs);
for (int j = 0; j < 4; j++)
{
int k = startLocsClockwise ? j : 3 - j; // ie reverse result path
Add(rectPath_[k]);
AddToEdge(edges_[k * 2], results_[0]!);
}
}
else 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));
}
}
public Paths64 Execute(Paths64 paths)
{
Paths64 result = new Paths64();
if (rect_.IsEmpty()) return result;
foreach (Path64 path in paths)
{
if (path.Count < 3) continue;
pathBounds_ = Clipper.GetBounds(path);
if (!rect_.Intersects(pathBounds_))
continue; // the path must be completely outside fRect
if (rect_.Contains(pathBounds_))
{
// the path must be completely inside rect_
result.Add(path);
continue;
}
ExecuteInternal(path);
CheckEdges();
for (int i = 0; i < 4; ++i)
TidyEdgePair(i, edges_[i * 2], edges_[i * 2 + 1]);
foreach (OutPt2? op in results_)
{
Path64 tmp = GetPath(op);
if (tmp.Count > 0) result.Add(tmp);
}
//clean up after every loop
results_.Clear();
for (int i = 0; i < 8; i++)
edges_[i].Clear();
}
return result;
}
private void CheckEdges()
{
for (int i = 0; i < results_.Count; i++)
{
OutPt2? op = results_[i], op2 = op;
if (op == null) continue;
do
{
if (InternalClipper.IsCollinear(
op2!.prev!.pt, op2.pt, op2.next!.pt))
{
if (op2 == op)
{
op2 = UnlinkOpBack(op2);
if (op2 == null) break;
op = op2.prev;
}
else
{
op2 = UnlinkOpBack(op2);
if (op2 == null) break;
}
}
else
op2 = op2.next;
} while (op2 != op);
if (op2 == null)
{
results_[i] = null;
continue;
}
results_[i] = op2; // safety first
uint edgeSet1 = GetEdgesForPt(op!.prev!.pt, rect_);
op2 = op;
do
{
uint edgeSet2 = GetEdgesForPt(op2!.pt, rect_);
if (edgeSet2 != 0 && op2.edge == null)
{
uint combinedSet = (edgeSet1 & edgeSet2);
for (int j = 0; j < 4; ++j)
{
if ((combinedSet & (1 << j)) == 0) continue;
if (IsHeadingClockwise(op2.prev!.pt, op2.pt, j))
AddToEdge(edges_[j * 2], op2);
else
AddToEdge(edges_[j * 2 + 1], op2);
}
}
edgeSet1 = edgeSet2;
op2 = op2.next;
} while (op2 != op);
}
}
private void TidyEdgePair(int idx, List<OutPt2?> cw, List<OutPt2?> ccw)
{
if (ccw.Count == 0) return;
bool isHorz = ((idx == 1) || (idx == 3));
bool cwIsTowardLarger = ((idx == 1) || (idx == 2));
int i = 0, j = 0;
while (i < cw.Count)
{
OutPt2? p1 = cw[i];
if (p1 == null || p1.next == p1.prev)
{
cw[i++] = null;
j = 0;
continue;
}
int jLim = ccw.Count;
while (j < jLim &&
(ccw[j] == null || ccw[j]!.next == ccw[j]!.prev)) ++j;
if (j == jLim)
{
++i;
j = 0;
continue;
}
OutPt2? p2;
OutPt2? p1a;
OutPt2? p2a;
if (cwIsTowardLarger)
{
// p1 >>>> p1a;
// p2 <<<< p2a;
p1 = cw[i]!.prev!;
p1a = cw[i];
p2 = ccw[j];
p2a = ccw[j]!.prev!;
}
else
{
// p1 <<<< p1a;
// p2 >>>> p2a;
p1 = cw[i];
p1a = cw[i]!.prev!;
p2 = ccw[j]!.prev!;
p2a = ccw[j];
}
if ((isHorz && !HasHorzOverlap(p1!.pt, p1a!.pt, p2!.pt, p2a!.pt)) ||
(!isHorz && !HasVertOverlap(p1!.pt, p1a!.pt, p2!.pt, p2a!.pt)))
{
++j;
continue;
}
// to get here we're either splitting or rejoining
bool isRejoining = cw[i]!.ownerIdx != ccw[j]!.ownerIdx;
if (isRejoining)
{
results_[p2!.ownerIdx] = null;
SetNewOwner(p2, p1!.ownerIdx);
}
// do the split or re-join
if (cwIsTowardLarger)
{
// p1 >> | >> p1a;
// p2 << | << p2a;
p1!.next = p2;
p2!.prev = p1;
p1a!.prev = p2a;
p2a!.next = p1a;
}
else
{
// p1 << | << p1a;
// p2 >> | >> p2a;
p1!.prev = p2;
p2!.next = p1;
p1a!.next = p2a;
p2a!.prev = p1a;
}
if (!isRejoining)
{
int new_idx = results_.Count;
results_.Add(p1a);
SetNewOwner(p1a, new_idx);
}
OutPt2? op;
OutPt2? op2;
if (cwIsTowardLarger)
{
op = p2;
op2 = p1a;
}
else
{
op = p1;
op2 = p2a;
}
results_[op.ownerIdx] = op;
results_[op2.ownerIdx] = op2;
// and now lots of work to get ready for the next loop
bool opIsLarger, op2IsLarger;
if (isHorz) // X
{
opIsLarger = op.pt.X > op.prev!.pt.X;
op2IsLarger = op2.pt.X > op2.prev!.pt.X;
}
else // Y
{
opIsLarger = op.pt.Y > op.prev!.pt.Y;
op2IsLarger = op2.pt.Y > op2.prev!.pt.Y;
}
if ((op.next == op.prev) ||
(op.pt == op.prev.pt))
{
if (op2IsLarger == cwIsTowardLarger)
{
cw[i] = op2;
ccw[j++] = null;
}
else
{
ccw[j] = op2;
cw[i++] = null;
}
}
else if ((op2.next == op2.prev) ||
(op2.pt == op2.prev.pt))
{
if (opIsLarger == cwIsTowardLarger)
{
cw[i] = op;
ccw[j++] = null;
}
else
{
ccw[j] = op;
cw[i++] = null;
}
}
else if (opIsLarger == op2IsLarger)
{
if (opIsLarger == cwIsTowardLarger)
{
cw[i] = op;
UncoupleEdge(op2);
AddToEdge(cw, op2);
ccw[j++] = null;
}
else
{
cw[i++] = null;
ccw[j] = op2;
UncoupleEdge(op);
AddToEdge(ccw, op);
j = 0;
}
}
else
{
if (opIsLarger == cwIsTowardLarger)
cw[i] = op;
else
ccw[j] = op;
if (op2IsLarger == cwIsTowardLarger)
cw[i] = op2;
else
ccw[j] = op2;
}
}
}
private static Path64 GetPath(OutPt2? op)
{
Path64 result = new Path64();
if (op == null || op.prev == op.next) return result;
OutPt2? op2 = op.next;
while (op2 != null && op2 != op)
{
if (InternalClipper.IsCollinear(
op2.prev!.pt, op2.pt, op2.next!.pt))
{
op = op2.prev;
op2 = UnlinkOp(op2);
}
else
op2 = op2.next;
}
if (op2 == null) return new Path64();
result.Add(op.pt);
op2 = op.next;
while (op2 != op)
{
result.Add(op2!.pt);
op2 = op2.next;
}
return result;
}
} // RectClip class
public class RectClipLines64 : RectClip64
{
internal RectClipLines64(Rect64 rect) : base(rect) { }
public new Paths64 Execute(Paths64 paths)
{
Paths64 result = new Paths64();
if (rect_.IsEmpty()) return result;
foreach (Path64 path in paths)
{
if (path.Count < 2) continue;
pathBounds_ = Clipper.GetBounds(path);
if (!rect_.Intersects(pathBounds_))
continue; // the path must be completely outside fRect
// Apart from that, we can't be sure whether the path
// is completely outside or completed inside or intersects
// fRect, simply by comparing path bounds with fRect.
ExecuteInternal(path);
foreach (OutPt2? op in results_)
{
Path64 tmp = GetPath(op);
if (tmp.Count > 0) result.Add(tmp);
}
//clean up after every loop
results_.Clear();
for(int i = 0; i < 8; i++)
edges_[i].Clear();
}
return result;
}
private static Path64 GetPath(OutPt2? op)
{
Path64 result = new Path64();
if (op == null || op == op.next) return result;
op = op.next; // starting at path beginning
result.Add(op!.pt);
OutPt2 op2 = op.next!;
while (op2 != op)
{
result.Add(op2.pt);
op2 = op2.next!;
}
return result;
}
private void ExecuteInternal(Path64 path)
{
results_.Clear();
if (path.Count < 2 || rect_.IsEmpty()) return;
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)
{
foreach (Point64 pt in path) Add(pt);
return;
}
if (prev == Location.inside) loc = Location.inside;
i = 1;
}
if (loc == Location.inside) 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
{
Add(ip, true);
}
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);
Add(ip2, true);
Add(ip);
}
else // path must be exiting rect
{
Add(ip);
}
} //while i <= highI
///////////////////////////////////////////////////
} // RectClipLines.ExecuteInternal
} // RectClipLines class
} // namespace