Created
February 15, 2012 23:05
-
-
Save qwertie/1839878 to your computer and use it in GitHub Desktop.
Refactored Clipper library (Vatti polygon intersection/union/difference)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************* | |
* * | |
* Author : Angus Johnson * | |
* Edited by : David Piepgrass * | |
* Version : 4.7 + edits * | |
* Date : 15 February 2012 * | |
* Website : http://www.angusj.com * | |
* Copyright : Angus Johnson 2010-2012 * | |
* * | |
* Note: I, David Piepgrass, refactored this library from its original version * | |
* in order to make it a little easier to follow. The following changes have * | |
* been made: * | |
* - This library contains several distinct algorithms, the biggest of which * | |
* is the Vatti clipping algorithm (in this file). The other algorithms are * | |
* Area, Orientation and OffsetPolygons (to compute the area, orientation, * | |
* or larger/smaller version of a polygon). The original code put everything * | |
* together in a single class; I separated the "minor" algorithms out into * | |
* a separate class, PolygonMath, that the Clipper class does not depend on. * | |
* I placed basic and shared data types in ClipperCommon.cs, so that all the * | |
* code remaining in this file is specifically related to polygon clipping. * | |
* I eliminated all dependencies from PolygonMath to Clipper except in one * | |
* method. * | |
* - I moved the data types that are only used internally by Clipper into a new * | |
* "ClipperLib.ClipperImpl" namespace. * | |
* - I added a few doc-comments here and there. * | |
* - I renamed IntPoint to PointL (because it contains longs, not ints, and * | |
* following the naming convention of System.Drawing.PointF). Similarly * | |
* IntRect is now RectangleL, named after System.Drawing.RectangleF. * | |
* - I changed Int128 from a "class" to a "struct" and made it immutable (from * | |
* an external perspective), implicitly fixing the code of operator+ so it * | |
* no longer modifies its first argument. * | |
* - I changed PointL from a "class" to a "struct". In fact, the code was * | |
* already written as though it is a struct, since every method that mutates * | |
* the coordinates of a point already uses "ref" arguments. I also added * | |
* ToString() and operator==. (Some people say mutable structs are evil, but * | |
* I think mutable structs are easier to reason about than mutable classes, * | |
* because when you pass one "by value" to a method, you can be certain * | |
* that the method will not modify your copy. Besides, polygons consume half * | |
* as much memory on x64 if PointL is a struct.) * | |
* - I ran a "format document" command because the original indentation was * | |
* inconsistent, and the contents of some blocks were simply not indented. * | |
* * | |
* License: * | |
* Use, modification & distribution is subject to Boost Software License Ver 1. * | |
* http://www.boost.org/LICENSE_1_0.txt * | |
* * | |
* Attributions: * | |
* The code in this library is an extension of Bala Vatti's clipping algorithm: * | |
* "A generic solution to polygon clipping" * | |
* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * | |
* http://portal.acm.org/citation.cfm?id=129906 * | |
* * | |
* Computer graphics and geometric modeling: implementation and algorithms * | |
* By Max K. Agoston * | |
* Springer; 1 edition (January 4, 2005) * | |
* http://books.google.com/books?q=vatti+clipping+agoston * | |
* * | |
* See also: * | |
* "Polygon Offsetting by Computing Winding Numbers" * | |
* Paper no. DETC2005-85513 pp. 565-575 * | |
* ASME 2005 International Design Engineering Technical Conferences * | |
* and Computers and Information in Engineering Conference (IDETC/CIE2005) * | |
* September 24–28, 2005 , Long Beach, California, USA * | |
* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * | |
* * | |
*******************************************************************************/ | |
/******************************************************************************* | |
* * | |
* This is a translation of the Delphi Clipper library and the naming style * | |
* used has retained a Delphi flavour. * | |
* * | |
*******************************************************************************/ | |
using System; | |
using System.Collections.Generic; | |
using System.Text; //for Int128.AsString() & StringBuilder | |
// Implementation details | |
namespace ClipperLib.ClipperImpl | |
{ | |
using Polygon = List<PointL>; | |
using Polygons = List<List<PointL>>; | |
using ExPolygons = List<ExPolygon>; | |
internal enum EdgeSide { Left, Right }; | |
internal enum Direction { RightToLeft, LeftToRight }; | |
[Flags] | |
internal enum Protects { None = 0, Left = 1, Right = 2, Both = 3 }; | |
internal class TEdge | |
{ | |
public long xbot; | |
public long ybot; | |
public long xcurr; | |
public long ycurr; | |
public long xtop; | |
public long ytop; | |
public double dx; | |
public long tmpX; | |
public PolyType polyType; | |
public EdgeSide side; | |
public int windDelta; //1 or -1 depending on winding direction | |
public int windCnt; | |
public int windCnt2; //winding count of the opposite polytype | |
public int outIdx; | |
public TEdge next; | |
public TEdge prev; | |
public TEdge nextInLML; | |
public TEdge nextInAEL; | |
public TEdge prevInAEL; | |
public TEdge nextInSEL; | |
public TEdge prevInSEL; | |
}; | |
internal class IntersectNode | |
{ | |
public TEdge edge1; | |
public TEdge edge2; | |
public PointL pt; | |
public IntersectNode next; | |
}; | |
internal class LocalMinima | |
{ | |
public long Y; | |
public TEdge leftBound; | |
public TEdge rightBound; | |
public LocalMinima next; | |
}; | |
internal class Scanbeam | |
{ | |
public long Y; | |
public Scanbeam next; | |
}; | |
internal class OutRec | |
{ | |
public int idx; | |
public bool isHole; | |
public OutRec FirstLeft; | |
public OutRec AppendLink; | |
public OutPt pts; | |
public OutPt bottomPt; | |
public TEdge bottomE1; | |
public TEdge bottomE2; | |
}; | |
internal class OutPt | |
{ | |
public int idx; | |
public PointL pt; | |
public OutPt next; | |
public OutPt prev; | |
}; | |
internal class JoinRec | |
{ | |
public PointL pt1a; | |
public PointL pt1b; | |
public int poly1Idx; | |
public PointL pt2a; | |
public PointL pt2b; | |
public int poly2Idx; | |
}; | |
internal class HorzJoinRec | |
{ | |
public TEdge edge; | |
public int savedIdx; | |
}; | |
} | |
namespace ClipperLib | |
{ | |
using ClipperLib.ClipperImpl; | |
using Polygon = List<PointL>; | |
using Polygons = List<List<PointL>>; | |
using ExPolygons = List<ExPolygon>; | |
public enum ClipType { Intersection, Union, Difference, Xor }; | |
public enum PolyType { Subject, Clip }; | |
//By far the most widely used winding rules for polygon filling are | |
//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) | |
//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) | |
//see http://glprogramming.com/red/chapter11.html | |
public enum PolyFillType { EvenOdd, NonZero, Positive, Negative }; | |
public class ClipperBase | |
{ | |
protected const double Horizontal = -3.4E+38; | |
internal const long LoRange = 1518500249; //sqrt(2^63 -1)/2 | |
internal const long HiRange = 6521908912666391106L; //sqrt(2^127 -1)/2 | |
internal LocalMinima m_MinimaList; | |
internal LocalMinima m_CurrentLM; | |
internal List<List<TEdge>> m_edges = new List<List<TEdge>>(); | |
internal bool m_UseFullRange; | |
//------------------------------------------------------------------------------ | |
internal bool PointIsVertex(PointL pt, OutPt pp) | |
{ | |
OutPt pp2 = pp; | |
do { | |
if (pp2.pt == pt) | |
return true; | |
pp2 = pp2.next; | |
} while (pp2 != pp); | |
return false; | |
} | |
//------------------------------------------------------------------------------ | |
internal bool PointInPolygon(PointL pt, OutPt pp, bool UseFulllongRange) | |
{ | |
OutPt pp2 = pp; | |
bool result = false; | |
if (UseFulllongRange) { | |
do { | |
if ((((pp2.pt.Y <= pt.Y) && (pt.Y < pp2.prev.pt.Y)) || | |
((pp2.prev.pt.Y <= pt.Y) && (pt.Y < pp2.pt.Y))) && | |
new Int128(pt.X - pp2.pt.X) < | |
Int128.Mul(pp2.prev.pt.X - pp2.pt.X, pt.Y - pp2.pt.Y) / | |
new Int128(pp2.prev.pt.Y - pp2.pt.Y)) | |
result = !result; | |
pp2 = pp2.next; | |
} while (pp2 != pp); | |
} else { | |
do { | |
if ((((pp2.pt.Y <= pt.Y) && (pt.Y < pp2.prev.pt.Y)) || | |
((pp2.prev.pt.Y <= pt.Y) && (pt.Y < pp2.pt.Y))) && | |
(pt.X - pp2.pt.X < (pp2.prev.pt.X - pp2.pt.X) * (pt.Y - pp2.pt.Y) / | |
(pp2.prev.pt.Y - pp2.pt.Y))) | |
result = !result; | |
pp2 = pp2.next; | |
} while (pp2 != pp); | |
} | |
return result; | |
} | |
//------------------------------------------------------------------------------ | |
internal bool SlopesEqual(TEdge e1, TEdge e2, bool UseFullRange) | |
{ | |
if (UseFullRange) | |
return Int128.Mul(e1.ytop - e1.ybot, e2.xtop - e2.xbot) == | |
Int128.Mul(e1.xtop - e1.xbot, e2.ytop - e2.ybot); | |
else | |
return (long)(e1.ytop - e1.ybot) * (e2.xtop - e2.xbot) - | |
(long)(e1.xtop - e1.xbot) * (e2.ytop - e2.ybot) == 0; | |
} | |
//------------------------------------------------------------------------------ | |
protected bool SlopesEqual(PointL pt1, PointL pt2, | |
PointL pt3, bool UseFullRange) | |
{ | |
if (UseFullRange) | |
return Int128.Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) == | |
Int128.Mul(pt1.X - pt2.X, pt2.Y - pt3.Y); | |
else | |
return | |
(long)(pt1.Y - pt2.Y) * (pt2.X - pt3.X) - (long)(pt1.X - pt2.X) * (pt2.Y - pt3.Y) == 0; | |
} | |
//------------------------------------------------------------------------------ | |
protected bool SlopesEqual(PointL pt1, PointL pt2, | |
PointL pt3, PointL pt4, bool UseFullRange) | |
{ | |
if (UseFullRange) | |
return Int128.Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) == | |
Int128.Mul(pt1.X - pt2.X, pt3.Y - pt4.Y); | |
else | |
return | |
(long)(pt1.Y - pt2.Y) * (pt3.X - pt4.X) - (long)(pt1.X - pt2.X) * (pt3.Y - pt4.Y) == 0; | |
} | |
//------------------------------------------------------------------------------ | |
internal ClipperBase() //constructor (nb: no external instantiation) | |
{ | |
m_MinimaList = null; | |
m_CurrentLM = null; | |
m_UseFullRange = false; | |
} | |
//------------------------------------------------------------------------------ | |
//destructor - commented out since I gather this impedes the GC | |
//~ClipperBase() | |
//{ | |
// Clear(); | |
//} | |
//------------------------------------------------------------------------------ | |
public virtual void Clear() | |
{ | |
DisposeLocalMinimaList(); | |
for (int i = 0; i < m_edges.Count; ++i) { | |
for (int j = 0; j < m_edges[i].Count; ++j) | |
m_edges[i][j] = null; | |
m_edges[i].Clear(); | |
} | |
m_edges.Clear(); | |
m_UseFullRange = false; | |
} | |
//------------------------------------------------------------------------------ | |
private void DisposeLocalMinimaList() | |
{ | |
while (m_MinimaList != null) { | |
LocalMinima tmpLm = m_MinimaList.next; | |
m_MinimaList = null; | |
m_MinimaList = tmpLm; | |
} | |
m_CurrentLM = null; | |
} | |
//------------------------------------------------------------------------------ | |
public bool AddPolygons(Polygons ppg, PolyType polyType) | |
{ | |
bool result = false; | |
for (int i = 0; i < ppg.Count; ++i) | |
if (AddPolygon(ppg[i], polyType)) | |
result = true; | |
return result; | |
} | |
//------------------------------------------------------------------------------ | |
public bool AddPolygon(Polygon pg, PolyType polyType) | |
{ | |
int len = pg.Count; | |
if (len < 3) | |
return false; | |
Polygon p = new Polygon(len); | |
p.Add(new PointL(pg[0].X, pg[0].Y)); | |
int j = 0; | |
for (int i = 1; i < len; ++i) { | |
long maxVal; | |
if (m_UseFullRange) | |
maxVal = HiRange; | |
else | |
maxVal = LoRange; | |
if (Math.Abs(pg[i].X) > maxVal || Math.Abs(pg[i].Y) > maxVal) { | |
if (m_UseFullRange) | |
throw new ClipperException("Coordinate exceeds range bounds"); | |
maxVal = HiRange; | |
if (Math.Abs(pg[i].X) > maxVal || Math.Abs(pg[i].Y) > maxVal) | |
throw new ClipperException("Coordinate exceeds range bounds"); | |
m_UseFullRange = true; | |
} | |
if (p[j] == pg[i]) | |
continue; | |
else if (j > 0 && SlopesEqual(p[j - 1], p[j], pg[i], m_UseFullRange)) { | |
if (p[j - 1] == pg[i]) | |
j--; | |
} else | |
j++; | |
if (j < p.Count) | |
p[j] = pg[i]; | |
else | |
p.Add(new PointL(pg[i].X, pg[i].Y)); | |
} | |
if (j < 2) | |
return false; | |
len = j + 1; | |
for (; ; ) { | |
//nb: test for point equality before testing slopes ... | |
if (p[j] == p[0]) | |
j--; | |
else if (p[0] == p[1] || SlopesEqual(p[j], p[0], p[1], m_UseFullRange)) | |
p[0] = p[j--]; | |
else if (SlopesEqual(p[j - 1], p[j], p[0], m_UseFullRange)) | |
j--; | |
else if (SlopesEqual(p[0], p[1], p[2], m_UseFullRange)) { | |
for (int i = 2; i <= j; ++i) | |
p[i - 1] = p[i]; | |
j--; | |
} | |
//exit loop if nothing is changed or there are too few vertices ... | |
if (j == len - 1 || j < 2) | |
break; | |
len = j + 1; | |
} | |
if (len < 3) | |
return false; | |
//create a new edge array ... | |
List<TEdge> edges = new List<TEdge>(len); | |
for (int i = 0; i < len; i++) | |
edges.Add(new TEdge()); | |
m_edges.Add(edges); | |
//convert vertices to a double-linked-list of edges and initialize ... | |
edges[0].xcurr = p[0].X; | |
edges[0].ycurr = p[0].Y; | |
InitEdge(edges[len - 1], edges[0], edges[len - 2], p[len - 1], polyType); | |
for (int i = len - 2; i > 0; --i) | |
InitEdge(edges[i], edges[i + 1], edges[i - 1], p[i], polyType); | |
InitEdge(edges[0], edges[1], edges[len - 1], p[0], polyType); | |
//reset xcurr & ycurr and find 'eHighest' (given the Y axis coordinates | |
//increase downward so the 'highest' edge will have the smallest ytop) ... | |
TEdge e = edges[0]; | |
TEdge eHighest = e; | |
do { | |
e.xcurr = e.xbot; | |
e.ycurr = e.ybot; | |
if (e.ytop < eHighest.ytop) | |
eHighest = e; | |
e = e.next; | |
} while (e != edges[0]); | |
//make sure eHighest is positioned so the following loop works safely ... | |
if (eHighest.windDelta > 0) | |
eHighest = eHighest.next; | |
if (eHighest.dx == Horizontal) | |
eHighest = eHighest.next; | |
//finally insert each local minima ... | |
e = eHighest; | |
do { | |
e = AddBoundsToLML(e); | |
} while (e != eHighest); | |
return true; | |
} | |
//------------------------------------------------------------------------------ | |
private void InitEdge(TEdge e, TEdge eNext, | |
TEdge ePrev, PointL pt, PolyType polyType) | |
{ | |
e.next = eNext; | |
e.prev = ePrev; | |
e.xcurr = pt.X; | |
e.ycurr = pt.Y; | |
if (e.ycurr >= e.next.ycurr) { | |
e.xbot = e.xcurr; | |
e.ybot = e.ycurr; | |
e.xtop = e.next.xcurr; | |
e.ytop = e.next.ycurr; | |
e.windDelta = 1; | |
} else { | |
e.xtop = e.xcurr; | |
e.ytop = e.ycurr; | |
e.xbot = e.next.xcurr; | |
e.ybot = e.next.ycurr; | |
e.windDelta = -1; | |
} | |
SetDx(e); | |
e.polyType = polyType; | |
e.outIdx = -1; | |
} | |
//------------------------------------------------------------------------------ | |
private void SetDx(TEdge e) | |
{ | |
if (e.ybot == e.ytop) | |
e.dx = Horizontal; | |
else | |
e.dx = (double)(e.xtop - e.xbot) / (e.ytop - e.ybot); | |
} | |
//--------------------------------------------------------------------------- | |
TEdge AddBoundsToLML(TEdge e) | |
{ | |
//Starting at the top of one bound we progress to the bottom where there's | |
//a local minima. We then go to the top of the next bound. These two bounds | |
//form the left and right (or right and left) bounds of the local minima. | |
e.nextInLML = null; | |
e = e.next; | |
for (; ; ) { | |
if (e.dx == Horizontal) { | |
//nb: proceed through horizontals when approaching from their right, | |
// but break on horizontal minima if approaching from their left. | |
// This ensures 'local minima' are always on the left of horizontals. | |
if (e.next.ytop < e.ytop && e.next.xbot > e.prev.xbot) | |
break; | |
if (e.xtop != e.prev.xbot) | |
SwapX(e); | |
e.nextInLML = e.prev; | |
} else if (e.ycurr == e.prev.ycurr) | |
break; | |
else | |
e.nextInLML = e.prev; | |
e = e.next; | |
} | |
//e and e.prev are now at a local minima ... | |
LocalMinima newLm = new LocalMinima(); | |
newLm.next = null; | |
newLm.Y = e.prev.ybot; | |
if (e.dx == Horizontal) //horizontal edges never start a left bound | |
{ | |
if (e.xbot != e.prev.xbot) | |
SwapX(e); | |
newLm.leftBound = e.prev; | |
newLm.rightBound = e; | |
} else if (e.dx < e.prev.dx) { | |
newLm.leftBound = e.prev; | |
newLm.rightBound = e; | |
} else { | |
newLm.leftBound = e; | |
newLm.rightBound = e.prev; | |
} | |
newLm.leftBound.side = EdgeSide.Left; | |
newLm.rightBound.side = EdgeSide.Right; | |
InsertLocalMinima(newLm); | |
for (; ; ) { | |
if (e.next.ytop == e.ytop && e.next.dx != Horizontal) | |
break; | |
e.nextInLML = e.next; | |
e = e.next; | |
if (e.dx == Horizontal && e.xbot != e.prev.xtop) | |
SwapX(e); | |
} | |
return e.next; | |
} | |
//------------------------------------------------------------------------------ | |
private void InsertLocalMinima(LocalMinima newLm) | |
{ | |
if (m_MinimaList == null) { | |
m_MinimaList = newLm; | |
} else if (newLm.Y >= m_MinimaList.Y) { | |
newLm.next = m_MinimaList; | |
m_MinimaList = newLm; | |
} else { | |
LocalMinima tmpLm = m_MinimaList; | |
while (tmpLm.next != null && (newLm.Y < tmpLm.next.Y)) | |
tmpLm = tmpLm.next; | |
newLm.next = tmpLm.next; | |
tmpLm.next = newLm; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
protected void PopLocalMinima() | |
{ | |
if (m_CurrentLM == null) | |
return; | |
m_CurrentLM = m_CurrentLM.next; | |
} | |
//------------------------------------------------------------------------------ | |
private void SwapX(TEdge e) | |
{ | |
//swap horizontal edges' top and bottom x's so they follow the natural | |
//progression of the bounds - ie so their xbots will align with the | |
//adjoining lower edge. [Helpful in the ProcessHorizontal() method.] | |
e.xcurr = e.xtop; | |
e.xtop = e.xbot; | |
e.xbot = e.xcurr; | |
} | |
//------------------------------------------------------------------------------ | |
protected virtual void Reset() | |
{ | |
m_CurrentLM = m_MinimaList; | |
//reset all edges ... | |
LocalMinima lm = m_MinimaList; | |
while (lm != null) { | |
TEdge e = lm.leftBound; | |
while (e != null) { | |
e.xcurr = e.xbot; | |
e.ycurr = e.ybot; | |
e.side = EdgeSide.Left; | |
e.outIdx = -1; | |
e = e.nextInLML; | |
} | |
e = lm.rightBound; | |
while (e != null) { | |
e.xcurr = e.xbot; | |
e.ycurr = e.ybot; | |
e.side = EdgeSide.Right; | |
e.outIdx = -1; | |
e = e.nextInLML; | |
} | |
lm = lm.next; | |
} | |
return; | |
} | |
//------------------------------------------------------------------------------ | |
public RectangleL GetBounds() | |
{ | |
RectangleL result = new RectangleL(); | |
LocalMinima lm = m_MinimaList; | |
if (lm == null) | |
return result; | |
result.left = lm.leftBound.xbot; | |
result.top = lm.leftBound.ybot; | |
result.right = lm.leftBound.xbot; | |
result.bottom = lm.leftBound.ybot; | |
while (lm != null) { | |
if (lm.leftBound.ybot > result.bottom) | |
result.bottom = lm.leftBound.ybot; | |
TEdge e = lm.leftBound; | |
for (; ; ) { | |
TEdge bottomE = e; | |
while (e.nextInLML != null) { | |
if (e.xbot < result.left) | |
result.left = e.xbot; | |
if (e.xbot > result.right) | |
result.right = e.xbot; | |
e = e.nextInLML; | |
} | |
if (e.xbot < result.left) | |
result.left = e.xbot; | |
if (e.xbot > result.right) | |
result.right = e.xbot; | |
if (e.xtop < result.left) | |
result.left = e.xtop; | |
if (e.xtop > result.right) | |
result.right = e.xtop; | |
if (e.ytop < result.top) | |
result.top = e.ytop; | |
if (bottomE == lm.leftBound) | |
e = lm.rightBound; | |
else | |
break; | |
} | |
lm = lm.next; | |
} | |
return result; | |
} | |
protected internal static long Round(double value) | |
{ | |
if ((value < 0)) | |
return (long)(value - 0.5); | |
else | |
return (long)(value + 0.5); | |
} | |
//------------------------------------------------------------------------------ | |
} //ClipperBase | |
/// <summary> | |
/// Implements the Vatti clipping algorithm, a fast general algorithm for | |
/// clipping any number of arbitrarily shaped subject polygons by any number | |
/// of arbitrarily shaped clip polygons. This class can compute not only the | |
/// intersection of these polygons, but also their union, difference or | |
/// exclusive-or. | |
/// </summary> | |
/// <remarks> | |
/// This implementation expects integer input points (<see cref="PointL"/>), | |
/// although it still uses floating-point math internally for some operations. | |
/// <para/> | |
/// Usage: use List{PointL} to define each polygon, then call <see cref="AddPolygon"/> | |
/// or <see cref="AddPolygons"/> to add each polygon to a list stored inside the | |
/// Clipper. Then call <see cref="Execute"/> to perform the desired clipping | |
/// operation. | |
/// </remarks> | |
public class Clipper : ClipperBase | |
{ | |
private List<OutRec> m_PolyOuts; | |
private ClipType m_ClipType; | |
private Scanbeam m_Scanbeam; | |
private TEdge m_ActiveEdges; | |
private TEdge m_SortedEdges; | |
private IntersectNode m_IntersectNodes; | |
private bool m_ExecuteLocked; | |
private PolyFillType m_ClipFillType; | |
private PolyFillType m_SubjFillType; | |
private List<JoinRec> m_Joins; | |
private List<HorzJoinRec> m_HorizJoins; | |
private bool m_ReverseOutput; | |
public Clipper() | |
{ | |
m_Scanbeam = null; | |
m_ActiveEdges = null; | |
m_SortedEdges = null; | |
m_IntersectNodes = null; | |
m_ExecuteLocked = false; | |
m_PolyOuts = new List<OutRec>(); | |
m_Joins = new List<JoinRec>(); | |
m_HorizJoins = new List<HorzJoinRec>(); | |
m_ReverseOutput = false; | |
} | |
//------------------------------------------------------------------------------ | |
//destructor - commented out since I gather this impedes the GC | |
//~Clipper() //destructor | |
//{ | |
// Clear(); | |
// DisposeScanbeamList(); | |
//} | |
//------------------------------------------------------------------------------ | |
public override void Clear() | |
{ | |
if (m_edges.Count == 0) | |
return; //avoids problems with ClipperBase destructor | |
DisposeAllPolyPts(); | |
base.Clear(); | |
} | |
//------------------------------------------------------------------------------ | |
void DisposeScanbeamList() | |
{ | |
while (m_Scanbeam != null) { | |
Scanbeam sb2 = m_Scanbeam.next; | |
m_Scanbeam = null; | |
m_Scanbeam = sb2; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
protected override void Reset() | |
{ | |
base.Reset(); | |
m_Scanbeam = null; | |
m_ActiveEdges = null; | |
m_SortedEdges = null; | |
DisposeAllPolyPts(); | |
LocalMinima lm = m_MinimaList; | |
while (lm != null) { | |
InsertScanbeam(lm.Y); | |
InsertScanbeam(lm.leftBound.ytop); | |
lm = lm.next; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
public bool ReverseSolution | |
{ | |
get { return m_ReverseOutput; } | |
set { m_ReverseOutput = value; } | |
} | |
//------------------------------------------------------------------------------ | |
private void InsertScanbeam(long Y) | |
{ | |
if (m_Scanbeam == null) { | |
m_Scanbeam = new Scanbeam(); | |
m_Scanbeam.next = null; | |
m_Scanbeam.Y = Y; | |
} else if (Y > m_Scanbeam.Y) { | |
Scanbeam newSb = new Scanbeam(); | |
newSb.Y = Y; | |
newSb.next = m_Scanbeam; | |
m_Scanbeam = newSb; | |
} else { | |
Scanbeam sb2 = m_Scanbeam; | |
while (sb2.next != null && (Y <= sb2.next.Y)) | |
sb2 = sb2.next; | |
if (Y == sb2.Y) | |
return; //ie ignores duplicates | |
Scanbeam newSb = new Scanbeam(); | |
newSb.Y = Y; | |
newSb.next = sb2.next; | |
sb2.next = newSb; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
public bool Execute(ClipType clipType, Polygons solution, | |
PolyFillType subjFillType, PolyFillType clipFillType) | |
{ | |
if (m_ExecuteLocked) | |
return false; | |
m_ExecuteLocked = true; | |
solution.Clear(); | |
m_SubjFillType = subjFillType; | |
m_ClipFillType = clipFillType; | |
m_ClipType = clipType; | |
bool succeeded = ExecuteInternal(false); | |
//build the return polygons ... | |
if (succeeded) | |
BuildResult(solution); | |
m_ExecuteLocked = false; | |
return succeeded; | |
} | |
//------------------------------------------------------------------------------ | |
public bool Execute(ClipType clipType, ExPolygons solution, | |
PolyFillType subjFillType, PolyFillType clipFillType) | |
{ | |
if (m_ExecuteLocked) | |
return false; | |
m_ExecuteLocked = true; | |
solution.Clear(); | |
m_SubjFillType = subjFillType; | |
m_ClipFillType = clipFillType; | |
m_ClipType = clipType; | |
bool succeeded = ExecuteInternal(true); | |
//build the return polygons ... | |
if (succeeded) | |
BuildResultEx(solution); | |
m_ExecuteLocked = false; | |
return succeeded; | |
} | |
//------------------------------------------------------------------------------ | |
public bool Execute(ClipType clipType, Polygons solution) | |
{ | |
return Execute(clipType, solution, | |
PolyFillType.EvenOdd, PolyFillType.EvenOdd); | |
} | |
//------------------------------------------------------------------------------ | |
public bool Execute(ClipType clipType, ExPolygons solution) | |
{ | |
return Execute(clipType, solution, | |
PolyFillType.EvenOdd, PolyFillType.EvenOdd); | |
} | |
//------------------------------------------------------------------------------ | |
internal int PolySort(OutRec or1, OutRec or2) | |
{ | |
if (or1 == or2) | |
return 0; | |
else if (or1.pts == null || or2.pts == null) { | |
if ((or1.pts == null) != (or2.pts == null)) { | |
if (or1.pts != null) | |
return -1; | |
else | |
return 1; | |
} else | |
return 0; | |
} | |
int i1, i2; | |
if (or1.isHole) | |
i1 = or1.FirstLeft.idx; | |
else | |
i1 = or1.idx; | |
if (or2.isHole) | |
i2 = or2.FirstLeft.idx; | |
else | |
i2 = or2.idx; | |
int result = i1 - i2; | |
if (result == 0 && (or1.isHole != or2.isHole)) { | |
if (or1.isHole) | |
return 1; | |
else | |
return -1; | |
} | |
return result; | |
} | |
//------------------------------------------------------------------------------ | |
internal OutRec FindAppendLinkEnd(OutRec outRec) | |
{ | |
while (outRec.AppendLink != null) | |
outRec = outRec.AppendLink; | |
return outRec; | |
} | |
//------------------------------------------------------------------------------ | |
internal void FixHoleLinkage(OutRec outRec) | |
{ | |
OutRec tmp; | |
if (outRec.bottomPt != null) | |
tmp = m_PolyOuts[outRec.bottomPt.idx].FirstLeft; | |
else | |
tmp = outRec.FirstLeft; | |
if (outRec == tmp) | |
throw new ClipperException("HoleLinkage error"); | |
if (tmp != null) { | |
if (tmp.AppendLink != null) | |
tmp = FindAppendLinkEnd(tmp); | |
if (tmp == outRec) | |
tmp = null; | |
else if (tmp.isHole) { | |
FixHoleLinkage(tmp); | |
tmp = tmp.FirstLeft; | |
} | |
} | |
outRec.FirstLeft = tmp; | |
if (tmp == null) | |
outRec.isHole = false; | |
outRec.AppendLink = null; | |
} | |
//------------------------------------------------------------------------------ | |
private bool ExecuteInternal(bool fixHoleLinkages) | |
{ | |
bool succeeded; | |
try { | |
Reset(); | |
if (m_CurrentLM == null) | |
return true; | |
long botY = PopScanbeam(); | |
do { | |
InsertLocalMinimaIntoAEL(botY); | |
m_HorizJoins.Clear(); | |
ProcessHorizontals(); | |
long topY = PopScanbeam(); | |
succeeded = ProcessIntersections(botY, topY); | |
if (!succeeded) | |
break; | |
ProcessEdgesAtTopOfScanbeam(topY); | |
botY = topY; | |
} while (m_Scanbeam != null); | |
} catch { succeeded = false; } | |
if (succeeded) { | |
//tidy up output polygons and fix orientations where necessary ... | |
foreach (OutRec outRec in m_PolyOuts) { | |
if (outRec.pts == null) | |
continue; | |
FixupOutPolygon(outRec); | |
if (outRec.pts == null) | |
continue; | |
if (outRec.isHole && fixHoleLinkages) | |
FixHoleLinkage(outRec); | |
if (outRec.isHole == (m_ReverseOutput ^ Orientation(outRec, m_UseFullRange))) | |
ReversePolyPtLinks(outRec.pts); | |
} | |
JoinCommonEdges(fixHoleLinkages); | |
if (fixHoleLinkages) | |
m_PolyOuts.Sort(new Comparison<OutRec>(PolySort)); | |
} | |
m_Joins.Clear(); | |
m_HorizJoins.Clear(); | |
return succeeded; | |
} | |
//------------------------------------------------------------------------------ | |
private long PopScanbeam() | |
{ | |
long Y = m_Scanbeam.Y; | |
Scanbeam sb2 = m_Scanbeam; | |
m_Scanbeam = m_Scanbeam.next; | |
sb2 = null; | |
return Y; | |
} | |
//------------------------------------------------------------------------------ | |
private void DisposeAllPolyPts() | |
{ | |
for (int i = 0; i < m_PolyOuts.Count; ++i) | |
DisposeOutRec(i, false); | |
m_PolyOuts.Clear(); | |
} | |
//------------------------------------------------------------------------------ | |
void DisposeOutRec(int index, bool ignorePts) | |
{ | |
OutRec outRec = m_PolyOuts[index]; | |
if (!ignorePts && outRec.pts != null) | |
DisposeOutPts(outRec.pts); | |
outRec = null; | |
m_PolyOuts[index] = null; | |
} | |
//------------------------------------------------------------------------------ | |
private void DisposeOutPts(OutPt pp) | |
{ | |
if (pp == null) | |
return; | |
OutPt tmpPp = null; | |
pp.prev.next = null; | |
while (pp != null) { | |
tmpPp = pp; | |
pp = pp.next; | |
tmpPp = null; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void AddJoin(TEdge e1, TEdge e2, int e1OutIdx, int e2OutIdx) | |
{ | |
JoinRec jr = new JoinRec(); | |
if (e1OutIdx >= 0) | |
jr.poly1Idx = e1OutIdx; | |
else | |
jr.poly1Idx = e1.outIdx; | |
jr.pt1a = new PointL(e1.xcurr, e1.ycurr); | |
jr.pt1b = new PointL(e1.xtop, e1.ytop); | |
if (e2OutIdx >= 0) | |
jr.poly2Idx = e2OutIdx; | |
else | |
jr.poly2Idx = e2.outIdx; | |
jr.pt2a = new PointL(e2.xcurr, e2.ycurr); | |
jr.pt2b = new PointL(e2.xtop, e2.ytop); | |
m_Joins.Add(jr); | |
} | |
//------------------------------------------------------------------------------ | |
private void AddHorzJoin(TEdge e, int idx) | |
{ | |
HorzJoinRec hj = new HorzJoinRec(); | |
hj.edge = e; | |
hj.savedIdx = idx; | |
m_HorizJoins.Add(hj); | |
} | |
//------------------------------------------------------------------------------ | |
private void InsertLocalMinimaIntoAEL(long botY) | |
{ | |
while (m_CurrentLM != null && (m_CurrentLM.Y == botY)) | |
{ | |
TEdge lb = m_CurrentLM.leftBound; | |
TEdge rb = m_CurrentLM.rightBound; | |
InsertEdgeIntoAEL(lb); | |
InsertScanbeam(lb.ytop); | |
InsertEdgeIntoAEL(rb); | |
if (IsEvenOddFillType(lb)) { | |
lb.windDelta = 1; | |
rb.windDelta = 1; | |
} else { | |
rb.windDelta = -lb.windDelta; | |
} | |
SetWindingCount(lb); | |
rb.windCnt = lb.windCnt; | |
rb.windCnt2 = lb.windCnt2; | |
if (rb.dx == Horizontal) { | |
//nb: only rightbounds can have a horizontal bottom edge | |
AddEdgeToSEL(rb); | |
InsertScanbeam(rb.nextInLML.ytop); | |
} else | |
InsertScanbeam(rb.ytop); | |
if (IsContributing(lb)) | |
AddLocalMinPoly(lb, rb, new PointL(lb.xcurr, m_CurrentLM.Y)); | |
//if any output polygons share an edge, they'll need joining later ... | |
if (rb.outIdx >= 0) { | |
if (rb.dx == Horizontal) { | |
for (int i = 0; i < m_HorizJoins.Count; i++) { | |
PointL pt = new PointL(), pt2 = new PointL(); //used as dummy params. | |
HorzJoinRec hj = m_HorizJoins[i]; | |
//if horizontals rb and hj.edge overlap, flag for joining later ... | |
if (GetOverlapSegment(new PointL(hj.edge.xbot, hj.edge.ybot), | |
new PointL(hj.edge.xtop, hj.edge.ytop), | |
new PointL(rb.xbot, rb.ybot), | |
new PointL(rb.xtop, rb.ytop), | |
ref pt, ref pt2)) | |
AddJoin(hj.edge, rb, hj.savedIdx, -1); | |
} | |
} | |
} | |
if (lb.nextInAEL != rb) { | |
if (rb.outIdx >= 0 && rb.prevInAEL.outIdx >= 0 && | |
SlopesEqual(rb.prevInAEL, rb, m_UseFullRange)) | |
AddJoin(rb, rb.prevInAEL, -1, -1); | |
TEdge e = lb.nextInAEL; | |
PointL pt = new PointL(lb.xcurr, lb.ycurr); | |
while (e != rb) { | |
if (e == null) | |
throw new ClipperException("InsertLocalMinimaIntoAEL: missing rightbound!"); | |
//nb: For calculating winding counts etc, IntersectEdges() assumes | |
//that param1 will be to the right of param2 ABOVE the intersection ... | |
IntersectEdges(rb, e, pt, Protects.None); //order important here | |
e = e.nextInAEL; | |
} | |
} | |
PopLocalMinima(); | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void InsertEdgeIntoAEL(TEdge edge) | |
{ | |
edge.prevInAEL = null; | |
edge.nextInAEL = null; | |
if (m_ActiveEdges == null) { | |
m_ActiveEdges = edge; | |
} else if (E2InsertsBeforeE1(m_ActiveEdges, edge)) { | |
edge.nextInAEL = m_ActiveEdges; | |
m_ActiveEdges.prevInAEL = edge; | |
m_ActiveEdges = edge; | |
} else { | |
TEdge e = m_ActiveEdges; | |
while (e.nextInAEL != null && !E2InsertsBeforeE1(e.nextInAEL, edge)) | |
e = e.nextInAEL; | |
edge.nextInAEL = e.nextInAEL; | |
if (e.nextInAEL != null) | |
e.nextInAEL.prevInAEL = edge; | |
edge.prevInAEL = e; | |
e.nextInAEL = edge; | |
} | |
} | |
//---------------------------------------------------------------------- | |
private bool E2InsertsBeforeE1(TEdge e1, TEdge e2) | |
{ | |
if (e2.xcurr == e1.xcurr) | |
return e2.dx > e1.dx; | |
else | |
return e2.xcurr < e1.xcurr; | |
} | |
//------------------------------------------------------------------------------ | |
private bool IsEvenOddFillType(TEdge edge) | |
{ | |
if (edge.polyType == PolyType.Subject) | |
return m_SubjFillType == PolyFillType.EvenOdd; | |
else | |
return m_ClipFillType == PolyFillType.EvenOdd; | |
} | |
//------------------------------------------------------------------------------ | |
private bool IsEvenOddAltFillType(TEdge edge) | |
{ | |
if (edge.polyType == PolyType.Subject) | |
return m_ClipFillType == PolyFillType.EvenOdd; | |
else | |
return m_SubjFillType == PolyFillType.EvenOdd; | |
} | |
//------------------------------------------------------------------------------ | |
private bool IsContributing(TEdge edge) | |
{ | |
PolyFillType pft, pft2; | |
if (edge.polyType == PolyType.Subject) { | |
pft = m_SubjFillType; | |
pft2 = m_ClipFillType; | |
} else { | |
pft = m_ClipFillType; | |
pft2 = m_SubjFillType; | |
} | |
switch (pft) { | |
case PolyFillType.EvenOdd: | |
case PolyFillType.NonZero: | |
if (Math.Abs(edge.windCnt) != 1) | |
return false; | |
break; | |
case PolyFillType.Positive: | |
if (edge.windCnt != 1) | |
return false; | |
break; | |
default: //PolyFillType.pftNegative | |
if (edge.windCnt != -1) | |
return false; | |
break; | |
} | |
switch (m_ClipType) { | |
case ClipType.Intersection: | |
switch (pft2) { | |
case PolyFillType.EvenOdd: | |
case PolyFillType.NonZero: | |
return (edge.windCnt2 != 0); | |
case PolyFillType.Positive: | |
return (edge.windCnt2 > 0); | |
default: | |
return (edge.windCnt2 < 0); | |
} | |
case ClipType.Union: | |
switch (pft2) { | |
case PolyFillType.EvenOdd: | |
case PolyFillType.NonZero: | |
return (edge.windCnt2 == 0); | |
case PolyFillType.Positive: | |
return (edge.windCnt2 <= 0); | |
default: | |
return (edge.windCnt2 >= 0); | |
} | |
case ClipType.Difference: | |
if (edge.polyType == PolyType.Subject) | |
switch (pft2) { | |
case PolyFillType.EvenOdd: | |
case PolyFillType.NonZero: | |
return (edge.windCnt2 == 0); | |
case PolyFillType.Positive: | |
return (edge.windCnt2 <= 0); | |
default: | |
return (edge.windCnt2 >= 0); | |
} else | |
switch (pft2) { | |
case PolyFillType.EvenOdd: | |
case PolyFillType.NonZero: | |
return (edge.windCnt2 != 0); | |
case PolyFillType.Positive: | |
return (edge.windCnt2 > 0); | |
default: | |
return (edge.windCnt2 < 0); | |
} | |
} | |
return true; | |
} | |
//------------------------------------------------------------------------------ | |
private void SetWindingCount(TEdge edge) | |
{ | |
TEdge e = edge.prevInAEL; | |
//find the edge of the same polytype that immediately preceeds 'edge' in AEL | |
while (e != null && e.polyType != edge.polyType) | |
e = e.prevInAEL; | |
if (e == null) { | |
edge.windCnt = edge.windDelta; | |
edge.windCnt2 = 0; | |
e = m_ActiveEdges; //ie get ready to calc windCnt2 | |
} else if (IsEvenOddFillType(edge)) { | |
//even-odd filling ... | |
edge.windCnt = 1; | |
edge.windCnt2 = e.windCnt2; | |
e = e.nextInAEL; //ie get ready to calc windCnt2 | |
} else { | |
//nonZero filling ... | |
if (e.windCnt * e.windDelta < 0) { | |
if (Math.Abs(e.windCnt) > 1) { | |
if (e.windDelta * edge.windDelta < 0) | |
edge.windCnt = e.windCnt; | |
else | |
edge.windCnt = e.windCnt + edge.windDelta; | |
} else | |
edge.windCnt = e.windCnt + e.windDelta + edge.windDelta; | |
} else { | |
if (Math.Abs(e.windCnt) > 1 && e.windDelta * edge.windDelta < 0) | |
edge.windCnt = e.windCnt; | |
else if (e.windCnt + edge.windDelta == 0) | |
edge.windCnt = e.windCnt; | |
else | |
edge.windCnt = e.windCnt + edge.windDelta; | |
} | |
edge.windCnt2 = e.windCnt2; | |
e = e.nextInAEL; //ie get ready to calc windCnt2 | |
} | |
//update windCnt2 ... | |
if (IsEvenOddAltFillType(edge)) { | |
//even-odd filling ... | |
while (e != edge) { | |
edge.windCnt2 = (edge.windCnt2 == 0) ? 1 : 0; | |
e = e.nextInAEL; | |
} | |
} else { | |
//nonZero filling ... | |
while (e != edge) { | |
edge.windCnt2 += e.windDelta; | |
e = e.nextInAEL; | |
} | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void AddEdgeToSEL(TEdge edge) | |
{ | |
//SEL pointers in PEdge are reused to build a list of horizontal edges. | |
//However, we don't need to worry about order with horizontal edge processing. | |
if (m_SortedEdges == null) { | |
m_SortedEdges = edge; | |
edge.prevInSEL = null; | |
edge.nextInSEL = null; | |
} else { | |
edge.nextInSEL = m_SortedEdges; | |
edge.prevInSEL = null; | |
m_SortedEdges.prevInSEL = edge; | |
m_SortedEdges = edge; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void CopyAELToSEL() | |
{ | |
TEdge e = m_ActiveEdges; | |
m_SortedEdges = e; | |
if (m_ActiveEdges == null) | |
return; | |
m_SortedEdges.prevInSEL = null; | |
e = e.nextInAEL; | |
while (e != null) { | |
e.prevInSEL = e.prevInAEL; | |
e.prevInSEL.nextInSEL = e; | |
e.nextInSEL = null; | |
e = e.nextInAEL; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void SwapPositionsInAEL(TEdge edge1, TEdge edge2) | |
{ | |
if (edge1.nextInAEL == null && edge1.prevInAEL == null) | |
return; | |
if (edge2.nextInAEL == null && edge2.prevInAEL == null) | |
return; | |
if (edge1.nextInAEL == edge2) { | |
TEdge next = edge2.nextInAEL; | |
if (next != null) | |
next.prevInAEL = edge1; | |
TEdge prev = edge1.prevInAEL; | |
if (prev != null) | |
prev.nextInAEL = edge2; | |
edge2.prevInAEL = prev; | |
edge2.nextInAEL = edge1; | |
edge1.prevInAEL = edge2; | |
edge1.nextInAEL = next; | |
} else if (edge2.nextInAEL == edge1) { | |
TEdge next = edge1.nextInAEL; | |
if (next != null) | |
next.prevInAEL = edge2; | |
TEdge prev = edge2.prevInAEL; | |
if (prev != null) | |
prev.nextInAEL = edge1; | |
edge1.prevInAEL = prev; | |
edge1.nextInAEL = edge2; | |
edge2.prevInAEL = edge1; | |
edge2.nextInAEL = next; | |
} else { | |
TEdge next = edge1.nextInAEL; | |
TEdge prev = edge1.prevInAEL; | |
edge1.nextInAEL = edge2.nextInAEL; | |
if (edge1.nextInAEL != null) | |
edge1.nextInAEL.prevInAEL = edge1; | |
edge1.prevInAEL = edge2.prevInAEL; | |
if (edge1.prevInAEL != null) | |
edge1.prevInAEL.nextInAEL = edge1; | |
edge2.nextInAEL = next; | |
if (edge2.nextInAEL != null) | |
edge2.nextInAEL.prevInAEL = edge2; | |
edge2.prevInAEL = prev; | |
if (edge2.prevInAEL != null) | |
edge2.prevInAEL.nextInAEL = edge2; | |
} | |
if (edge1.prevInAEL == null) | |
m_ActiveEdges = edge1; | |
else if (edge2.prevInAEL == null) | |
m_ActiveEdges = edge2; | |
} | |
//------------------------------------------------------------------------------ | |
private void SwapPositionsInSEL(TEdge edge1, TEdge edge2) | |
{ | |
if (edge1.nextInSEL == null && edge1.prevInSEL == null) | |
return; | |
if (edge2.nextInSEL == null && edge2.prevInSEL == null) | |
return; | |
if (edge1.nextInSEL == edge2) { | |
TEdge next = edge2.nextInSEL; | |
if (next != null) | |
next.prevInSEL = edge1; | |
TEdge prev = edge1.prevInSEL; | |
if (prev != null) | |
prev.nextInSEL = edge2; | |
edge2.prevInSEL = prev; | |
edge2.nextInSEL = edge1; | |
edge1.prevInSEL = edge2; | |
edge1.nextInSEL = next; | |
} else if (edge2.nextInSEL == edge1) { | |
TEdge next = edge1.nextInSEL; | |
if (next != null) | |
next.prevInSEL = edge2; | |
TEdge prev = edge2.prevInSEL; | |
if (prev != null) | |
prev.nextInSEL = edge1; | |
edge1.prevInSEL = prev; | |
edge1.nextInSEL = edge2; | |
edge2.prevInSEL = edge1; | |
edge2.nextInSEL = next; | |
} else { | |
TEdge next = edge1.nextInSEL; | |
TEdge prev = edge1.prevInSEL; | |
edge1.nextInSEL = edge2.nextInSEL; | |
if (edge1.nextInSEL != null) | |
edge1.nextInSEL.prevInSEL = edge1; | |
edge1.prevInSEL = edge2.prevInSEL; | |
if (edge1.prevInSEL != null) | |
edge1.prevInSEL.nextInSEL = edge1; | |
edge2.nextInSEL = next; | |
if (edge2.nextInSEL != null) | |
edge2.nextInSEL.prevInSEL = edge2; | |
edge2.prevInSEL = prev; | |
if (edge2.prevInSEL != null) | |
edge2.prevInSEL.nextInSEL = edge2; | |
} | |
if (edge1.prevInSEL == null) | |
m_SortedEdges = edge1; | |
else if (edge2.prevInSEL == null) | |
m_SortedEdges = edge2; | |
} | |
//------------------------------------------------------------------------------ | |
private void AddLocalMaxPoly(TEdge e1, TEdge e2, PointL pt) | |
{ | |
AddOutPt(e1, null, pt); | |
if (e1.outIdx == e2.outIdx) { | |
e1.outIdx = -1; | |
e2.outIdx = -1; | |
} else | |
AppendPolygon(e1, e2); | |
} | |
//------------------------------------------------------------------------------ | |
private void AddLocalMinPoly(TEdge e1, TEdge e2, PointL pt) | |
{ | |
TEdge e, prevE; | |
if (e2.dx == Horizontal || (e1.dx > e2.dx)) { | |
AddOutPt(e1, e2, pt); | |
e2.outIdx = e1.outIdx; | |
e1.side = EdgeSide.Left; | |
e2.side = EdgeSide.Right; | |
e = e1; | |
if (e.prevInAEL == e2) | |
prevE = e2.prevInAEL; | |
else | |
prevE = e.prevInAEL; | |
} else { | |
AddOutPt(e2, e1, pt); | |
e1.outIdx = e2.outIdx; | |
e1.side = EdgeSide.Right; | |
e2.side = EdgeSide.Left; | |
e = e2; | |
if (e.prevInAEL == e1) | |
prevE = e1.prevInAEL; | |
else | |
prevE = e.prevInAEL; | |
} | |
if (prevE != null && prevE.outIdx >= 0 && | |
(TopX(prevE, pt.Y) == TopX(e, pt.Y)) && | |
SlopesEqual(e, prevE, m_UseFullRange)) | |
AddJoin(e, prevE, -1, -1); | |
} | |
//------------------------------------------------------------------------------ | |
private OutRec CreateOutRec() | |
{ | |
OutRec result = new OutRec(); | |
result.idx = -1; | |
result.isHole = false; | |
result.FirstLeft = null; | |
result.AppendLink = null; | |
result.pts = null; | |
result.bottomPt = null; | |
return result; | |
} | |
//------------------------------------------------------------------------------ | |
private void AddOutPt(TEdge e, TEdge altE, PointL pt) | |
{ | |
bool ToFront = (e.side == EdgeSide.Left); | |
if (e.outIdx < 0) { | |
OutRec outRec = CreateOutRec(); | |
m_PolyOuts.Add(outRec); | |
outRec.idx = m_PolyOuts.Count - 1; | |
e.outIdx = outRec.idx; | |
OutPt op = new OutPt(); | |
outRec.pts = op; | |
outRec.bottomPt = op; | |
outRec.bottomE1 = e; | |
outRec.bottomE2 = altE; | |
op.pt = pt; | |
op.idx = outRec.idx; | |
op.next = op; | |
op.prev = op; | |
SetHoleState(e, outRec); | |
} else { | |
OutRec outRec = m_PolyOuts[e.outIdx]; | |
OutPt op = outRec.pts; | |
if (ToFront && pt == op.pt || | |
(!ToFront && pt == op.prev.pt)) | |
return; | |
OutPt op2 = new OutPt(); | |
op2.pt = pt; | |
op2.idx = outRec.idx; | |
if (op2.pt.Y == outRec.bottomPt.pt.Y && | |
op2.pt.X < outRec.bottomPt.pt.X) { | |
outRec.bottomPt = op2; | |
outRec.bottomE1 = e; | |
outRec.bottomE2 = altE; | |
} | |
op2.next = op; | |
op2.prev = op.prev; | |
op2.prev.next = op2; | |
op.prev = op2; | |
if (ToFront) | |
outRec.pts = op2; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
internal void SwapPoints(ref PointL pt1, ref PointL pt2) | |
{ | |
PointL tmp = pt1; | |
pt1 = pt2; | |
pt2 = tmp; | |
} | |
//------------------------------------------------------------------------------ | |
private bool GetOverlapSegment(PointL pt1a, PointL pt1b, PointL pt2a, | |
PointL pt2b, ref PointL pt1, ref PointL pt2) | |
{ | |
//precondition: segments are colinear. | |
if (pt1a.Y == pt1b.Y || Math.Abs((pt1a.X - pt1b.X) / (pt1a.Y - pt1b.Y)) > 1) { | |
if (pt1a.X > pt1b.X) | |
SwapPoints(ref pt1a, ref pt1b); | |
if (pt2a.X > pt2b.X) | |
SwapPoints(ref pt2a, ref pt2b); | |
if (pt1a.X > pt2a.X) | |
pt1 = pt1a; | |
else | |
pt1 = pt2a; | |
if (pt1b.X < pt2b.X) | |
pt2 = pt1b; | |
else | |
pt2 = pt2b; | |
return pt1.X < pt2.X; | |
} else { | |
if (pt1a.Y < pt1b.Y) | |
SwapPoints(ref pt1a, ref pt1b); | |
if (pt2a.Y < pt2b.Y) | |
SwapPoints(ref pt2a, ref pt2b); | |
if (pt1a.Y < pt2a.Y) | |
pt1 = pt1a; | |
else | |
pt1 = pt2a; | |
if (pt1b.Y > pt2b.Y) | |
pt2 = pt1b; | |
else | |
pt2 = pt2b; | |
return pt1.Y > pt2.Y; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private OutPt PolygonBottom(OutPt pp) | |
{ | |
OutPt p = pp.next; | |
OutPt result = pp; | |
while (p != pp) { | |
if (p.pt.Y > result.pt.Y) | |
result = p; | |
else if (p.pt.Y == result.pt.Y && p.pt.X < result.pt.X) | |
result = p; | |
p = p.next; | |
} | |
return result; | |
} | |
//------------------------------------------------------------------------------ | |
private bool FindSegment(ref OutPt pp, ref PointL pt1, ref PointL pt2) | |
{ | |
if (pp == null) | |
return false; | |
OutPt pp2 = pp; | |
PointL pt1a = new PointL(pt1); | |
PointL pt2a = new PointL(pt2); | |
do { | |
if (SlopesEqual(pt1a, pt2a, pp.pt, pp.prev.pt, true) && | |
SlopesEqual(pt1a, pt2a, pp.pt, true) && | |
GetOverlapSegment(pt1a, pt2a, pp.pt, pp.prev.pt, ref pt1, ref pt2)) | |
return true; | |
pp = pp.next; | |
} while (pp != pp2); | |
return false; | |
} | |
//------------------------------------------------------------------------------ | |
internal bool Pt3IsBetweenPt1AndPt2(PointL pt1, PointL pt2, PointL pt3) | |
{ | |
if (pt1 == pt3 || pt2 == pt3) | |
return true; | |
else if (pt1.X != pt2.X) | |
return (pt1.X < pt3.X) == (pt3.X < pt2.X); | |
else | |
return (pt1.Y < pt3.Y) == (pt3.Y < pt2.Y); | |
} | |
//------------------------------------------------------------------------------ | |
private OutPt InsertPolyPtBetween(OutPt p1, OutPt p2, PointL pt) | |
{ | |
OutPt result = new OutPt(); | |
result.pt = pt; | |
if (p2 == p1.next) { | |
p1.next = result; | |
p2.prev = result; | |
result.next = p2; | |
result.prev = p1; | |
} else { | |
p2.next = result; | |
p1.prev = result; | |
result.next = p1; | |
result.prev = p2; | |
} | |
return result; | |
} | |
//------------------------------------------------------------------------------ | |
private void SetHoleState(TEdge e, OutRec outRec) | |
{ | |
bool isHole = false; | |
TEdge e2 = e.prevInAEL; | |
while (e2 != null) { | |
if (e2.outIdx >= 0) { | |
isHole = !isHole; | |
if (outRec.FirstLeft == null) | |
outRec.FirstLeft = m_PolyOuts[e2.outIdx]; | |
} | |
e2 = e2.prevInAEL; | |
} | |
if (isHole) | |
outRec.isHole = true; | |
} | |
//------------------------------------------------------------------------------ | |
private double GetDx(PointL pt1, PointL pt2) | |
{ | |
if (pt1.Y == pt2.Y) | |
return Horizontal; | |
else | |
return (double)(pt2.X - pt1.X) / (double)(pt2.Y - pt1.Y); | |
} | |
//--------------------------------------------------------------------------- | |
private OutRec GetLowermostRec(OutRec outRec1, OutRec outRec2) | |
{ | |
//work out which polygon fragment has the correct hole state ... | |
OutPt bPt1 = outRec1.bottomPt; | |
OutPt bPt2 = outRec2.bottomPt; | |
if (bPt1.pt.Y > bPt2.pt.Y) | |
return outRec1; | |
else if (bPt1.pt.Y < bPt2.pt.Y) | |
return outRec2; | |
else if (bPt1.pt.X < bPt2.pt.X) | |
return outRec1; | |
else if (bPt1.pt.X > bPt2.pt.X) | |
return outRec2; | |
else if (outRec1.bottomE2 == null) | |
return outRec2; | |
else if (outRec2.bottomE2 == null) | |
return outRec1; | |
else { | |
long y1 = Math.Max(outRec1.bottomE1.ybot, outRec1.bottomE2.ybot); | |
long y2 = Math.Max(outRec2.bottomE1.ybot, outRec2.bottomE2.ybot); | |
if (y2 == y1 || (y1 > bPt1.pt.Y && y2 > bPt1.pt.Y)) { | |
double dx1 = Math.Max(outRec1.bottomE1.dx, outRec1.bottomE2.dx); | |
double dx2 = Math.Max(outRec2.bottomE1.dx, outRec2.bottomE2.dx); | |
if (dx2 > dx1) | |
return outRec2; | |
else | |
return outRec1; | |
} else if (y2 > y1) | |
return outRec2; | |
else | |
return outRec1; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void AppendPolygon(TEdge e1, TEdge e2) | |
{ | |
//get the start and ends of both output polygons ... | |
OutRec outRec1 = m_PolyOuts[e1.outIdx]; | |
OutRec outRec2 = m_PolyOuts[e2.outIdx]; | |
//work out which polygon fragment has the correct hole state ... | |
OutRec holeStateRec = GetLowermostRec(outRec1, outRec2); | |
//fixup hole status ... | |
if (holeStateRec == outRec2) | |
outRec1.isHole = outRec2.isHole; | |
else | |
outRec2.isHole = outRec1.isHole; | |
OutPt p1_lft = outRec1.pts; | |
OutPt p1_rt = p1_lft.prev; | |
OutPt p2_lft = outRec2.pts; | |
OutPt p2_rt = p2_lft.prev; | |
EdgeSide side; | |
//join e2 poly onto e1 poly and delete pointers to e2 ... | |
if (e1.side == EdgeSide.Left) { | |
if (e2.side == EdgeSide.Left) { | |
//z y x a b c | |
ReversePolyPtLinks(p2_lft); | |
p2_lft.next = p1_lft; | |
p1_lft.prev = p2_lft; | |
p1_rt.next = p2_rt; | |
p2_rt.prev = p1_rt; | |
outRec1.pts = p2_rt; | |
} else { | |
//x y z a b c | |
p2_rt.next = p1_lft; | |
p1_lft.prev = p2_rt; | |
p2_lft.prev = p1_rt; | |
p1_rt.next = p2_lft; | |
outRec1.pts = p2_lft; | |
} | |
side = EdgeSide.Left; | |
} else { | |
if (e2.side == EdgeSide.Right) { | |
//a b c z y x | |
ReversePolyPtLinks(p2_lft); | |
p1_rt.next = p2_rt; | |
p2_rt.prev = p1_rt; | |
p2_lft.next = p1_lft; | |
p1_lft.prev = p2_lft; | |
} else { | |
//a b c x y z | |
p1_rt.next = p2_lft; | |
p2_lft.prev = p1_rt; | |
p1_lft.prev = p2_rt; | |
p2_rt.next = p1_lft; | |
} | |
side = EdgeSide.Right; | |
} | |
if (holeStateRec == outRec2) { | |
outRec1.bottomPt = outRec2.bottomPt; | |
outRec1.bottomPt.idx = outRec1.idx; | |
outRec1.bottomE1 = outRec2.bottomE1; | |
outRec1.bottomE2 = outRec2.bottomE2; | |
if (outRec2.FirstLeft != outRec1) | |
outRec1.FirstLeft = outRec2.FirstLeft; | |
} | |
outRec2.pts = null; | |
outRec2.bottomPt = null; | |
outRec2.AppendLink = outRec1; | |
int OKIdx = e1.outIdx; | |
int ObsoleteIdx = e2.outIdx; | |
e1.outIdx = -1; //nb: safe because we only get here via AddLocalMaxPoly | |
e2.outIdx = -1; | |
TEdge e = m_ActiveEdges; | |
while (e != null) { | |
if (e.outIdx == ObsoleteIdx) { | |
e.outIdx = OKIdx; | |
e.side = side; | |
break; | |
} | |
e = e.nextInAEL; | |
} | |
for (int i = 0; i < m_Joins.Count; ++i) { | |
if (m_Joins[i].poly1Idx == ObsoleteIdx) | |
m_Joins[i].poly1Idx = OKIdx; | |
if (m_Joins[i].poly2Idx == ObsoleteIdx) | |
m_Joins[i].poly2Idx = OKIdx; | |
} | |
for (int i = 0; i < m_HorizJoins.Count; ++i) { | |
if (m_HorizJoins[i].savedIdx == ObsoleteIdx) | |
m_HorizJoins[i].savedIdx = OKIdx; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void ReversePolyPtLinks(OutPt pp) | |
{ | |
OutPt pp1; | |
OutPt pp2; | |
pp1 = pp; | |
do { | |
pp2 = pp1.next; | |
pp1.next = pp1.prev; | |
pp1.prev = pp2; | |
pp1 = pp2; | |
} while (pp1 != pp); | |
} | |
//------------------------------------------------------------------------------ | |
private static void SwapSides(TEdge edge1, TEdge edge2) | |
{ | |
EdgeSide side = edge1.side; | |
edge1.side = edge2.side; | |
edge2.side = side; | |
} | |
//------------------------------------------------------------------------------ | |
private static void SwapPolyIndexes(TEdge edge1, TEdge edge2) | |
{ | |
int outIdx = edge1.outIdx; | |
edge1.outIdx = edge2.outIdx; | |
edge2.outIdx = outIdx; | |
} | |
//------------------------------------------------------------------------------ | |
private void DoEdge1(TEdge edge1, TEdge edge2, PointL pt) | |
{ | |
AddOutPt(edge1, edge2, pt); | |
SwapSides(edge1, edge2); | |
SwapPolyIndexes(edge1, edge2); | |
} | |
//------------------------------------------------------------------------------ | |
private void DoEdge2(TEdge edge1, TEdge edge2, PointL pt) | |
{ | |
AddOutPt(edge2, edge1, pt); | |
SwapSides(edge1, edge2); | |
SwapPolyIndexes(edge1, edge2); | |
} | |
//------------------------------------------------------------------------------ | |
private void DoBothEdges(TEdge edge1, TEdge edge2, PointL pt) | |
{ | |
AddOutPt(edge1, edge2, pt); | |
AddOutPt(edge2, edge1, pt); | |
SwapSides(edge1, edge2); | |
SwapPolyIndexes(edge1, edge2); | |
} | |
//------------------------------------------------------------------------------ | |
private void IntersectEdges(TEdge e1, TEdge e2, PointL pt, Protects protects) | |
{ | |
//e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before | |
//e2 in AEL except when e1 is being inserted at the intersection point ... | |
bool e1stops = (Protects.Left & protects) == 0 && e1.nextInLML == null && | |
e1.xtop == pt.X && e1.ytop == pt.Y; | |
bool e2stops = (Protects.Right & protects) == 0 && e2.nextInLML == null && | |
e2.xtop == pt.X && e2.ytop == pt.Y; | |
bool e1Contributing = (e1.outIdx >= 0); | |
bool e2contributing = (e2.outIdx >= 0); | |
//update winding counts... | |
//assumes that e1 will be to the right of e2 ABOVE the intersection | |
if (e1.polyType == e2.polyType) { | |
if (IsEvenOddFillType(e1)) { | |
int oldE1WindCnt = e1.windCnt; | |
e1.windCnt = e2.windCnt; | |
e2.windCnt = oldE1WindCnt; | |
} else { | |
if (e1.windCnt + e2.windDelta == 0) | |
e1.windCnt = -e1.windCnt; | |
else | |
e1.windCnt += e2.windDelta; | |
if (e2.windCnt - e1.windDelta == 0) | |
e2.windCnt = -e2.windCnt; | |
else | |
e2.windCnt -= e1.windDelta; | |
} | |
} else { | |
if (!IsEvenOddFillType(e2)) | |
e1.windCnt2 += e2.windDelta; | |
else | |
e1.windCnt2 = (e1.windCnt2 == 0) ? 1 : 0; | |
if (!IsEvenOddFillType(e1)) | |
e2.windCnt2 -= e1.windDelta; | |
else | |
e2.windCnt2 = (e2.windCnt2 == 0) ? 1 : 0; | |
} | |
PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; | |
if (e1.polyType == PolyType.Subject) { | |
e1FillType = m_SubjFillType; | |
e1FillType2 = m_ClipFillType; | |
} else { | |
e1FillType = m_ClipFillType; | |
e1FillType2 = m_SubjFillType; | |
} | |
if (e2.polyType == PolyType.Subject) { | |
e2FillType = m_SubjFillType; | |
e2FillType2 = m_ClipFillType; | |
} else { | |
e2FillType = m_ClipFillType; | |
e2FillType2 = m_SubjFillType; | |
} | |
int e1Wc, e2Wc; | |
switch (e1FillType) { | |
case PolyFillType.Positive: | |
e1Wc = e1.windCnt; | |
break; | |
case PolyFillType.Negative: | |
e1Wc = -e1.windCnt; | |
break; | |
default: | |
e1Wc = Math.Abs(e1.windCnt); | |
break; | |
} | |
switch (e2FillType) { | |
case PolyFillType.Positive: | |
e2Wc = e2.windCnt; | |
break; | |
case PolyFillType.Negative: | |
e2Wc = -e2.windCnt; | |
break; | |
default: | |
e2Wc = Math.Abs(e2.windCnt); | |
break; | |
} | |
if (e1Contributing && e2contributing) { | |
if (e1stops || e2stops || | |
(e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || | |
(e1.polyType != e2.polyType && m_ClipType != ClipType.Xor)) | |
AddLocalMaxPoly(e1, e2, pt); | |
else | |
DoBothEdges(e1, e2, pt); | |
} else if (e1Contributing) { | |
if ((e2Wc == 0 || e2Wc == 1) && | |
(m_ClipType != ClipType.Intersection || | |
e2.polyType == PolyType.Subject || (e2.windCnt2 != 0))) | |
DoEdge1(e1, e2, pt); | |
} else if (e2contributing) { | |
if ((e1Wc == 0 || e1Wc == 1) && | |
(m_ClipType != ClipType.Intersection || | |
e1.polyType == PolyType.Subject || (e1.windCnt2 != 0))) | |
DoEdge2(e1, e2, pt); | |
} else if ((e1Wc == 0 || e1Wc == 1) && | |
(e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops) { | |
//neither edge is currently contributing ... | |
long e1Wc2, e2Wc2; | |
switch (e1FillType2) { | |
case PolyFillType.Positive: | |
e1Wc2 = e1.windCnt2; | |
break; | |
case PolyFillType.Negative: | |
e1Wc2 = -e1.windCnt2; | |
break; | |
default: | |
e1Wc2 = Math.Abs(e1.windCnt2); | |
break; | |
} | |
switch (e2FillType2) { | |
case PolyFillType.Positive: | |
e2Wc2 = e2.windCnt2; | |
break; | |
case PolyFillType.Negative: | |
e2Wc2 = -e2.windCnt2; | |
break; | |
default: | |
e2Wc2 = Math.Abs(e2.windCnt2); | |
break; | |
} | |
if (e1.polyType != e2.polyType) | |
AddLocalMinPoly(e1, e2, pt); | |
else if (e1Wc == 1 && e2Wc == 1) | |
switch (m_ClipType) { | |
case ClipType.Intersection: { | |
if (e1Wc2 > 0 && e2Wc2 > 0) | |
AddLocalMinPoly(e1, e2, pt); | |
break; | |
} | |
case ClipType.Union: { | |
if (e1Wc2 <= 0 && e2Wc2 <= 0) | |
AddLocalMinPoly(e1, e2, pt); | |
break; | |
} | |
case ClipType.Difference: { | |
if (e1.polyType == PolyType.Clip && e1Wc2 > 0 && e2Wc2 > 0 || | |
e1.polyType == PolyType.Subject && e1Wc2 <= 0 && e2Wc2 <= 0) | |
AddLocalMinPoly(e1, e2, pt); | |
break; | |
} | |
case ClipType.Xor: { | |
AddLocalMinPoly(e1, e2, pt); | |
break; | |
} | |
} else | |
SwapSides(e1, e2); | |
} | |
if ((e1stops != e2stops) && | |
((e1stops && (e1.outIdx >= 0)) || (e2stops && (e2.outIdx >= 0)))) { | |
SwapSides(e1, e2); | |
SwapPolyIndexes(e1, e2); | |
} | |
//finally, delete any non-contributing maxima edges ... | |
if (e1stops) | |
DeleteFromAEL(e1); | |
if (e2stops) | |
DeleteFromAEL(e2); | |
} | |
//------------------------------------------------------------------------------ | |
private void DeleteFromAEL(TEdge e) | |
{ | |
TEdge AelPrev = e.prevInAEL; | |
TEdge AelNext = e.nextInAEL; | |
if (AelPrev == null && AelNext == null && (e != m_ActiveEdges)) | |
return; //already deleted | |
if (AelPrev != null) | |
AelPrev.nextInAEL = AelNext; | |
else | |
m_ActiveEdges = AelNext; | |
if (AelNext != null) | |
AelNext.prevInAEL = AelPrev; | |
e.nextInAEL = null; | |
e.prevInAEL = null; | |
} | |
//------------------------------------------------------------------------------ | |
private void DeleteFromSEL(TEdge e) | |
{ | |
TEdge SelPrev = e.prevInSEL; | |
TEdge SelNext = e.nextInSEL; | |
if (SelPrev == null && SelNext == null && (e != m_SortedEdges)) | |
return; //already deleted | |
if (SelPrev != null) | |
SelPrev.nextInSEL = SelNext; | |
else | |
m_SortedEdges = SelNext; | |
if (SelNext != null) | |
SelNext.prevInSEL = SelPrev; | |
e.nextInSEL = null; | |
e.prevInSEL = null; | |
} | |
//------------------------------------------------------------------------------ | |
private void UpdateEdgeIntoAEL(ref TEdge e) | |
{ | |
if (e.nextInLML == null) | |
throw new ClipperException("UpdateEdgeIntoAEL: invalid call"); | |
TEdge AelPrev = e.prevInAEL; | |
TEdge AelNext = e.nextInAEL; | |
e.nextInLML.outIdx = e.outIdx; | |
if (AelPrev != null) | |
AelPrev.nextInAEL = e.nextInLML; | |
else | |
m_ActiveEdges = e.nextInLML; | |
if (AelNext != null) | |
AelNext.prevInAEL = e.nextInLML; | |
e.nextInLML.side = e.side; | |
e.nextInLML.windDelta = e.windDelta; | |
e.nextInLML.windCnt = e.windCnt; | |
e.nextInLML.windCnt2 = e.windCnt2; | |
e = e.nextInLML; | |
e.prevInAEL = AelPrev; | |
e.nextInAEL = AelNext; | |
if (e.dx != Horizontal) | |
InsertScanbeam(e.ytop); | |
} | |
//------------------------------------------------------------------------------ | |
private void ProcessHorizontals() | |
{ | |
TEdge horzEdge = m_SortedEdges; | |
while (horzEdge != null) { | |
DeleteFromSEL(horzEdge); | |
ProcessHorizontal(horzEdge); | |
horzEdge = m_SortedEdges; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void ProcessHorizontal(TEdge horzEdge) | |
{ | |
Direction Direction; | |
long horzLeft, horzRight; | |
if (horzEdge.xcurr < horzEdge.xtop) { | |
horzLeft = horzEdge.xcurr; | |
horzRight = horzEdge.xtop; | |
Direction = Direction.LeftToRight; | |
} else { | |
horzLeft = horzEdge.xtop; | |
horzRight = horzEdge.xcurr; | |
Direction = Direction.RightToLeft; | |
} | |
TEdge eMaxPair; | |
if (horzEdge.nextInLML != null) | |
eMaxPair = null; | |
else | |
eMaxPair = GetMaximaPair(horzEdge); | |
TEdge e = GetNextInAEL(horzEdge, Direction); | |
while (e != null) { | |
TEdge eNext = GetNextInAEL(e, Direction); | |
if (eMaxPair != null || | |
((Direction == Direction.LeftToRight) && (e.xcurr <= horzRight)) || | |
((Direction == Direction.RightToLeft) && (e.xcurr >= horzLeft))) { | |
//ok, so far it looks like we're still in range of the horizontal edge | |
if (e.xcurr == horzEdge.xtop && eMaxPair == null) { | |
if (SlopesEqual(e, horzEdge.nextInLML, m_UseFullRange)) { | |
//if output polygons share an edge, they'll need joining later ... | |
if (horzEdge.outIdx >= 0 && e.outIdx >= 0) | |
AddJoin(horzEdge.nextInLML, e, horzEdge.outIdx, -1); | |
break; //we've reached the end of the horizontal line | |
} else if (e.dx < horzEdge.nextInLML.dx) | |
//we really have got to the end of the intermediate horz edge so quit. | |
//nb: More -ve slopes follow more +ve slopes ABOVE the horizontal. | |
break; | |
} | |
if (e == eMaxPair) { | |
//horzEdge is evidently a maxima horizontal and we've arrived at its end. | |
if (Direction == Direction.LeftToRight) | |
IntersectEdges(horzEdge, e, new PointL(e.xcurr, horzEdge.ycurr), 0); | |
else | |
IntersectEdges(e, horzEdge, new PointL(e.xcurr, horzEdge.ycurr), 0); | |
if (eMaxPair.outIdx >= 0) | |
throw new ClipperException("ProcessHorizontal error"); | |
return; | |
} else if (e.dx == Horizontal && !IsMinima(e) && !(e.xcurr > e.xtop)) { | |
if (Direction == Direction.LeftToRight) | |
IntersectEdges(horzEdge, e, new PointL(e.xcurr, horzEdge.ycurr), | |
(IsTopHorz(horzEdge, e.xcurr)) ? Protects.Left : Protects.Both); | |
else | |
IntersectEdges(e, horzEdge, new PointL(e.xcurr, horzEdge.ycurr), | |
(IsTopHorz(horzEdge, e.xcurr)) ? Protects.Right : Protects.Both); | |
} else if (Direction == Direction.LeftToRight) { | |
IntersectEdges(horzEdge, e, new PointL(e.xcurr, horzEdge.ycurr), | |
(IsTopHorz(horzEdge, e.xcurr)) ? Protects.Left : Protects.Both); | |
} else { | |
IntersectEdges(e, horzEdge, new PointL(e.xcurr, horzEdge.ycurr), | |
(IsTopHorz(horzEdge, e.xcurr)) ? Protects.Right : Protects.Both); | |
} | |
SwapPositionsInAEL(horzEdge, e); | |
} else if ((Direction == Direction.LeftToRight && | |
e.xcurr > horzRight && horzEdge.nextInSEL == null) || | |
(Direction == Direction.RightToLeft && | |
e.xcurr < horzLeft && horzEdge.nextInSEL == null)) | |
break; | |
e = eNext; | |
} //end while ( e ) | |
if (horzEdge.nextInLML != null) { | |
if (horzEdge.outIdx >= 0) | |
AddOutPt(horzEdge, null, new PointL(horzEdge.xtop, horzEdge.ytop)); | |
UpdateEdgeIntoAEL(ref horzEdge); | |
} else { | |
if (horzEdge.outIdx >= 0) | |
IntersectEdges(horzEdge, eMaxPair, | |
new PointL(horzEdge.xtop, horzEdge.ycurr), Protects.Both); | |
DeleteFromAEL(eMaxPair); | |
DeleteFromAEL(horzEdge); | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private bool IsTopHorz(TEdge horzEdge, double XPos) | |
{ | |
TEdge e = m_SortedEdges; | |
while (e != null) { | |
if ((XPos >= Math.Min(e.xcurr, e.xtop)) && (XPos <= Math.Max(e.xcurr, e.xtop))) | |
return false; | |
e = e.nextInSEL; | |
} | |
return true; | |
} | |
//------------------------------------------------------------------------------ | |
private TEdge GetNextInAEL(TEdge e, Direction Direction) | |
{ | |
if (Direction == Direction.LeftToRight) | |
return e.nextInAEL; | |
else | |
return e.prevInAEL; | |
} | |
//------------------------------------------------------------------------------ | |
private bool IsMinima(TEdge e) | |
{ | |
return e != null && (e.prev.nextInLML != e) && (e.next.nextInLML != e); | |
} | |
//------------------------------------------------------------------------------ | |
private bool IsMaxima(TEdge e, double Y) | |
{ | |
return (e != null && e.ytop == Y && e.nextInLML == null); | |
} | |
//------------------------------------------------------------------------------ | |
private bool IsIntermediate(TEdge e, double Y) | |
{ | |
return (e.ytop == Y && e.nextInLML != null); | |
} | |
//------------------------------------------------------------------------------ | |
private TEdge GetMaximaPair(TEdge e) | |
{ | |
if (!IsMaxima(e.next, e.ytop) || (e.next.xtop != e.xtop)) | |
return e.prev; | |
else | |
return e.next; | |
} | |
//------------------------------------------------------------------------------ | |
private bool ProcessIntersections(long botY, long topY) | |
{ | |
if (m_ActiveEdges == null) | |
return true; | |
try { | |
BuildIntersectList(botY, topY); | |
if (m_IntersectNodes == null) | |
return true; | |
if (FixupIntersections()) | |
ProcessIntersectList(); | |
else | |
return false; | |
} catch { | |
m_SortedEdges = null; | |
DisposeIntersectNodes(); | |
throw new ClipperException("ProcessIntersections error"); | |
} | |
return true; | |
} | |
//------------------------------------------------------------------------------ | |
private void BuildIntersectList(long botY, long topY) | |
{ | |
if (m_ActiveEdges == null) | |
return; | |
//prepare for sorting ... | |
TEdge e = m_ActiveEdges; | |
e.tmpX = TopX(e, topY); | |
m_SortedEdges = e; | |
m_SortedEdges.prevInSEL = null; | |
e = e.nextInAEL; | |
while (e != null) { | |
e.prevInSEL = e.prevInAEL; | |
e.prevInSEL.nextInSEL = e; | |
e.nextInSEL = null; | |
e.tmpX = TopX(e, topY); | |
e = e.nextInAEL; | |
} | |
//bubblesort ... | |
bool isModified = true; | |
while (isModified && m_SortedEdges != null) { | |
isModified = false; | |
e = m_SortedEdges; | |
while (e.nextInSEL != null) { | |
TEdge eNext = e.nextInSEL; | |
PointL pt = new PointL(); | |
if (e.tmpX > eNext.tmpX && IntersectPoint(e, eNext, ref pt)) { | |
if (pt.Y > botY) { | |
pt.Y = botY; | |
pt.X = TopX(e, pt.Y); | |
} | |
AddIntersectNode(e, eNext, pt); | |
SwapPositionsInSEL(e, eNext); | |
isModified = true; | |
} else | |
e = eNext; | |
} | |
if (e.prevInSEL != null) | |
e.prevInSEL.nextInSEL = null; | |
else | |
break; | |
} | |
m_SortedEdges = null; | |
} | |
//------------------------------------------------------------------------------ | |
private bool FixupIntersections() | |
{ | |
if (m_IntersectNodes.next == null) | |
return true; | |
CopyAELToSEL(); | |
IntersectNode int1 = m_IntersectNodes; | |
IntersectNode int2 = m_IntersectNodes.next; | |
while (int2 != null) { | |
TEdge e1 = int1.edge1; | |
TEdge e2; | |
if (e1.prevInSEL == int1.edge2) | |
e2 = e1.prevInSEL; | |
else if (e1.nextInSEL == int1.edge2) | |
e2 = e1.nextInSEL; | |
else { | |
//The current intersection is out of order, so try and swap it with | |
//a subsequent intersection ... | |
while (int2 != null) { | |
if (int2.edge1.nextInSEL == int2.edge2 || | |
int2.edge1.prevInSEL == int2.edge2) | |
break; | |
else | |
int2 = int2.next; | |
} | |
if (int2 == null) | |
return false; //oops!!! | |
//found an intersect node that can be swapped ... | |
SwapIntersectNodes(int1, int2); | |
e1 = int1.edge1; | |
e2 = int1.edge2; | |
} | |
SwapPositionsInSEL(e1, e2); | |
int1 = int1.next; | |
int2 = int1.next; | |
} | |
m_SortedEdges = null; | |
//finally, check the last intersection too ... | |
return (int1.edge1.prevInSEL == int1.edge2 || int1.edge1.nextInSEL == int1.edge2); | |
} | |
//------------------------------------------------------------------------------ | |
private void ProcessIntersectList() | |
{ | |
while (m_IntersectNodes != null) { | |
IntersectNode iNode = m_IntersectNodes.next; | |
{ | |
IntersectEdges(m_IntersectNodes.edge1, | |
m_IntersectNodes.edge2, m_IntersectNodes.pt, Protects.Both); | |
SwapPositionsInAEL(m_IntersectNodes.edge1, m_IntersectNodes.edge2); | |
} | |
m_IntersectNodes = null; | |
m_IntersectNodes = iNode; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private static long TopX(TEdge edge, long currentY) | |
{ | |
if (currentY == edge.ytop) | |
return edge.xtop; | |
return edge.xbot + Round(edge.dx * (currentY - edge.ybot)); | |
} | |
//------------------------------------------------------------------------------ | |
private long TopX(PointL pt1, PointL pt2, long currentY) | |
{ | |
//preconditions: pt1.Y <> pt2.Y and pt1.Y > pt2.Y | |
if (currentY >= pt1.Y) | |
return pt1.X; | |
else if (currentY == pt2.Y) | |
return pt2.X; | |
else if (pt1.X == pt2.X) | |
return pt1.X; | |
else { | |
double q = (pt1.X - pt2.X) / (pt1.Y - pt2.Y); | |
return (long)Round(pt1.X + (currentY - pt1.Y) * q); | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void AddIntersectNode(TEdge e1, TEdge e2, PointL pt) | |
{ | |
IntersectNode newNode = new IntersectNode(); | |
newNode.edge1 = e1; | |
newNode.edge2 = e2; | |
newNode.pt = pt; | |
newNode.next = null; | |
if (m_IntersectNodes == null) | |
m_IntersectNodes = newNode; | |
else if (Process1Before2(newNode, m_IntersectNodes)) { | |
newNode.next = m_IntersectNodes; | |
m_IntersectNodes = newNode; | |
} else { | |
IntersectNode iNode = m_IntersectNodes; | |
while (iNode.next != null && Process1Before2(iNode.next, newNode)) | |
iNode = iNode.next; | |
newNode.next = iNode.next; | |
iNode.next = newNode; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private bool Process1Before2(IntersectNode node1, IntersectNode node2) | |
{ | |
bool result; | |
if (node1.pt.Y == node2.pt.Y) { | |
if (node1.edge1 == node2.edge1 || node1.edge2 == node2.edge1) { | |
result = node2.pt.X > node1.pt.X; | |
if (node2.edge1.dx > 0) | |
return !result; | |
else | |
return result; | |
} else if (node1.edge1 == node2.edge2 || node1.edge2 == node2.edge2) { | |
result = node2.pt.X > node1.pt.X; | |
if (node2.edge2.dx > 0) | |
return !result; | |
else | |
return result; | |
} else | |
return node2.pt.X > node1.pt.X; | |
} else | |
return node1.pt.Y > node2.pt.Y; | |
} | |
//------------------------------------------------------------------------------ | |
private void SwapIntersectNodes(IntersectNode int1, IntersectNode int2) | |
{ | |
TEdge e1 = int1.edge1; | |
TEdge e2 = int1.edge2; | |
PointL p = int1.pt; | |
int1.edge1 = int2.edge1; | |
int1.edge2 = int2.edge2; | |
int1.pt = int2.pt; | |
int2.edge1 = e1; | |
int2.edge2 = e2; | |
int2.pt = p; | |
} | |
//------------------------------------------------------------------------------ | |
private bool IntersectPoint(TEdge edge1, TEdge edge2, ref PointL ip) | |
{ | |
double b1, b2; | |
if (SlopesEqual(edge1, edge2, m_UseFullRange)) | |
return false; | |
else if (edge1.dx == 0) { | |
ip.X = edge1.xbot; | |
if (edge2.dx == Horizontal) { | |
ip.Y = edge2.ybot; | |
} else { | |
b2 = edge2.ybot - (edge2.xbot / edge2.dx); | |
ip.Y = Round(ip.X / edge2.dx + b2); | |
} | |
} else if (edge2.dx == 0) { | |
ip.X = edge2.xbot; | |
if (edge1.dx == Horizontal) { | |
ip.Y = edge1.ybot; | |
} else { | |
b1 = edge1.ybot - (edge1.xbot / edge1.dx); | |
ip.Y = Round(ip.X / edge1.dx + b1); | |
} | |
} else { | |
b1 = edge1.xbot - edge1.ybot * edge1.dx; | |
b2 = edge2.xbot - edge2.ybot * edge2.dx; | |
b2 = (b2 - b1) / (edge1.dx - edge2.dx); | |
ip.Y = Round(b2); | |
ip.X = Round(edge1.dx * b2 + b1); | |
} | |
return | |
//can be *so close* to the top of one edge that the rounded Y equals one ytop ... | |
(ip.Y == edge1.ytop && ip.Y >= edge2.ytop && edge1.tmpX > edge2.tmpX) || | |
(ip.Y == edge2.ytop && ip.Y >= edge1.ytop && edge1.tmpX > edge2.tmpX) || | |
(ip.Y > edge1.ytop && ip.Y > edge2.ytop); | |
} | |
//------------------------------------------------------------------------------ | |
private void DisposeIntersectNodes() | |
{ | |
while (m_IntersectNodes != null) { | |
IntersectNode iNode = m_IntersectNodes.next; | |
m_IntersectNodes = null; | |
m_IntersectNodes = iNode; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void ProcessEdgesAtTopOfScanbeam(long topY) | |
{ | |
TEdge e = m_ActiveEdges; | |
while (e != null) { | |
//1. process maxima, treating them as if they're 'bent' horizontal edges, | |
// but exclude maxima with horizontal edges. nb: e can't be a horizontal. | |
if (IsMaxima(e, topY) && GetMaximaPair(e).dx != Horizontal) { | |
//'e' might be removed from AEL, as may any following edges so ... | |
TEdge ePrior = e.prevInAEL; | |
DoMaxima(e, topY); | |
if (ePrior == null) | |
e = m_ActiveEdges; | |
else | |
e = ePrior.nextInAEL; | |
} else { | |
//2. promote horizontal edges, otherwise update xcurr and ycurr ... | |
if (IsIntermediate(e, topY) && e.nextInLML.dx == Horizontal) { | |
if (e.outIdx >= 0) { | |
AddOutPt(e, null, new PointL(e.xtop, e.ytop)); | |
for (int i = 0; i < m_HorizJoins.Count; ++i) { | |
PointL pt = new PointL(), pt2 = new PointL(); | |
HorzJoinRec hj = m_HorizJoins[i]; | |
if (GetOverlapSegment(new PointL(hj.edge.xbot, hj.edge.ybot), | |
new PointL(hj.edge.xtop, hj.edge.ytop), | |
new PointL(e.nextInLML.xbot, e.nextInLML.ybot), | |
new PointL(e.nextInLML.xtop, e.nextInLML.ytop), ref pt, ref pt2)) | |
AddJoin(hj.edge, e.nextInLML, hj.savedIdx, e.outIdx); | |
} | |
AddHorzJoin(e.nextInLML, e.outIdx); | |
} | |
UpdateEdgeIntoAEL(ref e); | |
AddEdgeToSEL(e); | |
} else { | |
//this just simplifies horizontal processing ... | |
e.xcurr = TopX(e, topY); | |
e.ycurr = topY; | |
} | |
e = e.nextInAEL; | |
} | |
} | |
//3. Process horizontals at the top of the scanbeam ... | |
ProcessHorizontals(); | |
//4. Promote intermediate vertices ... | |
e = m_ActiveEdges; | |
while (e != null) { | |
if (IsIntermediate(e, topY)) { | |
if (e.outIdx >= 0) | |
AddOutPt(e, null, new PointL(e.xtop, e.ytop)); | |
UpdateEdgeIntoAEL(ref e); | |
//if output polygons share an edge, they'll need joining later ... | |
if (e.outIdx >= 0 && e.prevInAEL != null && e.prevInAEL.outIdx >= 0 && | |
e.prevInAEL.xcurr == e.xbot && e.prevInAEL.ycurr == e.ybot && | |
SlopesEqual(new PointL(e.xbot, e.ybot), new PointL(e.xtop, e.ytop), | |
new PointL(e.xbot, e.ybot), | |
new PointL(e.prevInAEL.xtop, e.prevInAEL.ytop), m_UseFullRange)) { | |
AddOutPt(e.prevInAEL, null, new PointL(e.xbot, e.ybot)); | |
AddJoin(e, e.prevInAEL, -1, -1); | |
} else if (e.outIdx >= 0 && e.nextInAEL != null && e.nextInAEL.outIdx >= 0 && | |
e.nextInAEL.ycurr > e.nextInAEL.ytop && | |
e.nextInAEL.ycurr < e.nextInAEL.ybot && | |
e.nextInAEL.xcurr == e.xbot && e.nextInAEL.ycurr == e.ybot && | |
SlopesEqual(new PointL(e.xbot, e.ybot), new PointL(e.xtop, e.ytop), | |
new PointL(e.xbot, e.ybot), | |
new PointL(e.nextInAEL.xtop, e.nextInAEL.ytop), m_UseFullRange)) { | |
AddOutPt(e.nextInAEL, null, new PointL(e.xbot, e.ybot)); | |
AddJoin(e, e.nextInAEL, -1, -1); | |
} | |
} | |
e = e.nextInAEL; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void DoMaxima(TEdge e, long topY) | |
{ | |
TEdge eMaxPair = GetMaximaPair(e); | |
long X = e.xtop; | |
TEdge eNext = e.nextInAEL; | |
while (eNext != eMaxPair) { | |
if (eNext == null) | |
throw new ClipperException("DoMaxima error"); | |
IntersectEdges(e, eNext, new PointL(X, topY), Protects.Both); | |
eNext = eNext.nextInAEL; | |
} | |
if (e.outIdx < 0 && eMaxPair.outIdx < 0) { | |
DeleteFromAEL(e); | |
DeleteFromAEL(eMaxPair); | |
} else if (e.outIdx >= 0 && eMaxPair.outIdx >= 0) { | |
IntersectEdges(e, eMaxPair, new PointL(X, topY), Protects.None); | |
} else | |
throw new ClipperException("DoMaxima error"); | |
} | |
//------------------------------------------------------------------------------ | |
private bool Orientation(OutRec outRec, bool UseFull64BitRange) | |
{ | |
//first make sure bottomPt is correctly assigned ... | |
OutPt opBottom = outRec.pts, op = outRec.pts.next; | |
while (op != outRec.pts) { | |
if (op.pt.Y >= opBottom.pt.Y) { | |
if (op.pt.Y > opBottom.pt.Y || op.pt.X < opBottom.pt.X) | |
opBottom = op; | |
} | |
op = op.next; | |
} | |
outRec.bottomPt = opBottom; | |
//find vertices either side of bottomPt (skipping duplicate points) .... | |
OutPt opPrev = op.prev; | |
OutPt opNext = op.next; | |
while (op != opPrev && op.pt == opPrev.pt) | |
opPrev = opPrev.prev; | |
while (op != opNext && op.pt == opNext.pt) | |
opNext = opNext.next; | |
PointL vec1 = new PointL(op.pt.X - opPrev.pt.X, op.pt.Y - opPrev.pt.Y); | |
PointL vec2 = new PointL(opNext.pt.X - op.pt.X, opNext.pt.Y - op.pt.Y); | |
if (UseFull64BitRange) { | |
Int128 cross = Int128.Mul(vec1.X, vec2.Y) - Int128.Mul(vec2.X, vec1.Y); | |
return !cross.IsNegative(); | |
} else { | |
return (vec1.X * vec2.Y - vec2.X * vec1.Y) > 0; | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private int PointCount(OutPt pts) | |
{ | |
if (pts == null) | |
return 0; | |
int result = 0; | |
OutPt p = pts; | |
do { | |
result++; | |
p = p.next; | |
} while (p != pts); | |
return result; | |
} | |
//------------------------------------------------------------------------------ | |
private void BuildResult(Polygons polyg) | |
{ | |
polyg.Clear(); | |
polyg.Capacity = m_PolyOuts.Count; | |
foreach (OutRec outRec in m_PolyOuts) { | |
if (outRec.pts == null) | |
continue; | |
OutPt p = outRec.pts; | |
int cnt = PointCount(p); | |
if (cnt < 3) | |
continue; | |
Polygon pg = new Polygon(cnt); | |
for (int j = 0; j < cnt; j++) { | |
pg.Add(p.pt); | |
p = p.next; | |
} | |
polyg.Add(pg); | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void BuildResultEx(ExPolygons polyg) | |
{ | |
polyg.Clear(); | |
polyg.Capacity = m_PolyOuts.Count; | |
int i = 0; | |
while (i < m_PolyOuts.Count) { | |
OutRec outRec = m_PolyOuts[i++]; | |
if (outRec.pts == null) | |
break; //nb: already sorted here | |
OutPt p = outRec.pts; | |
int cnt = PointCount(p); | |
if (cnt < 3) | |
continue; | |
ExPolygon epg = new ExPolygon(); | |
epg.outer = new Polygon(cnt); | |
epg.holes = new Polygons(); | |
for (int j = 0; j < cnt; j++) { | |
epg.outer.Add(p.pt); | |
p = p.next; | |
} | |
while (i < m_PolyOuts.Count) { | |
outRec = m_PolyOuts[i]; | |
if (outRec.pts == null || !outRec.isHole) | |
break; | |
Polygon pg = new Polygon(); | |
p = outRec.pts; | |
do { | |
pg.Add(p.pt); | |
p = p.next; | |
} while (p != outRec.pts); | |
epg.holes.Add(pg); | |
i++; | |
} | |
polyg.Add(epg); | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void FixupOutPolygon(OutRec outRec) | |
{ | |
//FixupOutPolygon() - removes duplicate points and simplifies consecutive | |
//parallel edges by removing the middle vertex. | |
OutPt lastOK = null; | |
outRec.pts = outRec.bottomPt; | |
OutPt pp = outRec.bottomPt; | |
for (; ; ) { | |
if (pp.prev == pp || pp.prev == pp.next) { | |
DisposeOutPts(pp); | |
outRec.pts = null; | |
outRec.bottomPt = null; | |
return; | |
} | |
//test for duplicate points and for same slope (cross-product) ... | |
if (pp.pt == pp.next.pt || | |
SlopesEqual(pp.prev.pt, pp.pt, pp.next.pt, m_UseFullRange)) { | |
lastOK = null; | |
OutPt tmp = pp; | |
if (pp == outRec.bottomPt) { | |
if (tmp.prev.pt.Y > tmp.next.pt.Y) | |
outRec.bottomPt = tmp.prev; | |
else | |
outRec.bottomPt = tmp.next; | |
outRec.pts = outRec.bottomPt; | |
outRec.bottomPt.idx = outRec.idx; | |
} | |
pp.prev.next = pp.next; | |
pp.next.prev = pp.prev; | |
pp = pp.prev; | |
tmp = null; | |
} else if (pp == lastOK) | |
break; | |
else { | |
if (lastOK == null) | |
lastOK = pp; | |
pp = pp.next; | |
} | |
} | |
} | |
//------------------------------------------------------------------------------ | |
private void CheckHoleLinkages1(OutRec outRec1, OutRec outRec2) | |
{ | |
//when a polygon is split into 2 polygons, make sure any holes the original | |
//polygon contained link to the correct polygon ... | |
for (int i = 0; i < m_PolyOuts.Count; ++i) { | |
if (m_PolyOuts[i].isHole && m_PolyOuts[i].bottomPt != null && | |
m_PolyOuts[i].FirstLeft == outRec1 && | |
!PointInPolygon(m_PolyOuts[i].bottomPt.pt, | |
outRec1.pts, m_UseFullRange)) | |
m_PolyOuts[i].FirstLeft = outRec2; | |
} | |
} | |
//---------------------------------------------------------------------- | |
private void CheckHoleLinkages2(OutRec outRec1, OutRec outRec2) | |
{ | |
//if a hole is owned by outRec2 then make it owned by outRec1 ... | |
for (int i = 0; i < m_PolyOuts.Count; ++i) | |
if (m_PolyOuts[i].isHole && m_PolyOuts[i].bottomPt != null && | |
m_PolyOuts[i].FirstLeft == outRec2) | |
m_PolyOuts[i].FirstLeft = outRec1; | |
} | |
//---------------------------------------------------------------------- | |
private void JoinCommonEdges(bool fixHoleLinkages) | |
{ | |
for (int i = 0; i < m_Joins.Count; i++) { | |
JoinRec j = m_Joins[i]; | |
OutRec outRec1 = m_PolyOuts[j.poly1Idx]; | |
OutPt pp1a = outRec1.pts; | |
OutRec outRec2 = m_PolyOuts[j.poly2Idx]; | |
OutPt pp2a = outRec2.pts; | |
PointL pt1 = new PointL(j.pt2a); | |
PointL pt2 = new PointL(j.pt2b); | |
PointL pt3 = new PointL(j.pt1a); | |
PointL pt4 = new PointL(j.pt1b); | |
if (!FindSegment(ref pp1a, ref pt1, ref pt2)) | |
continue; | |
if (j.poly1Idx == j.poly2Idx) { | |
//we're searching the same polygon for overlapping segments so | |
//segment 2 mustn't be the same as segment 1 ... | |
pp2a = pp1a.next; | |
if (!FindSegment(ref pp2a, ref pt3, ref pt4) || (pp2a == pp1a)) | |
continue; | |
} else if (!FindSegment(ref pp2a, ref pt3, ref pt4)) | |
continue; | |
if (!GetOverlapSegment(pt1, pt2, pt3, pt4, ref pt1, ref pt2)) | |
continue; | |
OutPt p1, p2, p3, p4; | |
OutPt prev = pp1a.prev; | |
//get p1 & p2 polypts - the overlap start & endpoints on poly1 | |
if (pp1a.pt == pt1) | |
p1 = pp1a; | |
else if (prev.pt == pt1) | |
p1 = prev; | |
else | |
p1 = InsertPolyPtBetween(pp1a, prev, pt1); | |
if (pp1a.pt == pt2) | |
p2 = pp1a; | |
else if (prev.pt == pt2) | |
p2 = prev; | |
else if ((p1 == pp1a) || (p1 == prev)) | |
p2 = InsertPolyPtBetween(pp1a, prev, pt2); | |
else if (Pt3IsBetweenPt1AndPt2(pp1a.pt, p1.pt, pt2)) | |
p2 = InsertPolyPtBetween(pp1a, p1, pt2); | |
else | |
p2 = InsertPolyPtBetween(p1, prev, pt2); | |
//get p3 & p4 polypts - the overlap start & endpoints on poly2 | |
prev = pp2a.prev; | |
if (pp2a.pt == pt1) | |
p3 = pp2a; | |
else if (prev.pt == pt1) | |
p3 = prev; | |
else | |
p3 = InsertPolyPtBetween(pp2a, prev, pt1); | |
if (pp2a.pt == pt2) | |
p4 = pp2a; | |
else if (prev.pt == pt2) | |
p4 = prev; | |
else if ((p3 == pp2a) || (p3 == prev)) | |
p4 = InsertPolyPtBetween(pp2a, prev, pt2); | |
else if (Pt3IsBetweenPt1AndPt2(pp2a.pt, p3.pt, pt2)) | |
p4 = InsertPolyPtBetween(pp2a, p3, pt2); | |
else | |
p4 = InsertPolyPtBetween(p3, prev, pt2); | |
//p1.pt should equal p3.pt and p2.pt should equal p4.pt here, so ... | |
//join p1 to p3 and p2 to p4 ... | |
if (p1.next == p2 && p3.prev == p4) { | |
p1.next = p3; | |
p3.prev = p1; | |
p2.prev = p4; | |
p4.next = p2; | |
} else if (p1.prev == p2 && p3.next == p4) { | |
p1.prev = p3; | |
p3.next = p1; | |
p2.next = p4; | |
p4.prev = p2; | |
} else | |
continue; //an orientation is probably wrong | |
if (j.poly2Idx == j.poly1Idx) { | |
//instead of joining two polygons, we've just created a new one by | |
//splitting one polygon into two. | |
outRec1.pts = PolygonBottom(p1); | |
outRec1.bottomPt = outRec1.pts; | |
outRec1.bottomPt.idx = outRec1.idx; | |
outRec2 = CreateOutRec(); | |
m_PolyOuts.Add(outRec2); | |
outRec2.idx = m_PolyOuts.Count - 1; | |
j.poly2Idx = outRec2.idx; | |
outRec2.pts = PolygonBottom(p2); | |
outRec2.bottomPt = outRec2.pts; | |
outRec2.bottomPt.idx = outRec2.idx; | |
if (PointInPolygon(outRec2.pts.pt, outRec1.pts, m_UseFullRange)) { | |
//outRec1 is contained by outRec2 ... | |
outRec2.isHole = !outRec1.isHole; | |
outRec2.FirstLeft = outRec1; | |
if (outRec2.isHole == Orientation(outRec2, m_UseFullRange)) | |
ReversePolyPtLinks(outRec2.pts); | |
} else if (PointInPolygon(outRec1.pts.pt, outRec2.pts, m_UseFullRange)) { | |
//outRec2 is contained by outRec1 ... | |
outRec2.isHole = outRec1.isHole; | |
outRec1.isHole = !outRec2.isHole; | |
outRec2.FirstLeft = outRec1.FirstLeft; | |
outRec1.FirstLeft = outRec2; | |
if (outRec1.isHole == Orientation(outRec1, m_UseFullRange)) | |
ReversePolyPtLinks(outRec1.pts); | |
//make sure any contained holes now link to the correct polygon ... | |
if (fixHoleLinkages) | |
CheckHoleLinkages1(outRec1, outRec2); | |
} else { | |
outRec2.isHole = outRec1.isHole; | |
outRec2.FirstLeft = outRec1.FirstLeft; | |
//make sure any contained holes now link to the correct polygon ... | |
if (fixHoleLinkages) | |
CheckHoleLinkages1(outRec1, outRec2); | |
} | |
//now fixup any subsequent m_Joins that match this polygon | |
for (int k = i + 1; k < m_Joins.Count; k++) { | |
JoinRec j2 = m_Joins[k]; | |
if (j2.poly1Idx == j.poly1Idx && PointIsVertex(j2.pt1a, p2)) | |
j2.poly1Idx = j.poly2Idx; | |
if (j2.poly2Idx == j.poly1Idx && PointIsVertex(j2.pt2a, p2)) | |
j2.poly2Idx = j.poly2Idx; | |
} | |
//now cleanup redundant edges too ... | |
FixupOutPolygon(outRec1); | |
FixupOutPolygon(outRec2); | |
} else { | |
//joined 2 polygons together ... | |
//make sure any holes contained by outRec2 now link to outRec1 ... | |
if (fixHoleLinkages) | |
CheckHoleLinkages2(outRec1, outRec2); | |
//now cleanup redundant edges too ... | |
FixupOutPolygon(outRec1); | |
//sort out hole vs outer and then recheck orientation ... | |
if (outRec1.isHole != outRec2.isHole && | |
(outRec2.bottomPt.pt.Y > outRec1.bottomPt.pt.Y || | |
(outRec2.bottomPt.pt.Y == outRec1.bottomPt.pt.Y && | |
outRec2.bottomPt.pt.X < outRec1.bottomPt.pt.X))) | |
outRec1.isHole = outRec2.isHole; | |
if (outRec1.isHole == Orientation(outRec1, m_UseFullRange)) | |
ReversePolyPtLinks(outRec1.pts); | |
//delete the obsolete pointer ... | |
int OKIdx = outRec1.idx; | |
int ObsoleteIdx = outRec2.idx; | |
outRec2.pts = null; | |
outRec2.bottomPt = null; | |
outRec2.AppendLink = outRec1; | |
//now fixup any subsequent joins that match this polygon | |
for (int k = i + 1; k < m_Joins.Count; k++) { | |
JoinRec j2 = m_Joins[k]; | |
if (j2.poly1Idx == ObsoleteIdx) | |
j2.poly1Idx = OKIdx; | |
if (j2.poly2Idx == ObsoleteIdx) | |
j2.poly2Idx = OKIdx; | |
} | |
} | |
} | |
} | |
//------------------------------------------------------------------------------ | |
//------------------------------------------------------------------------------ | |
// OffsetPolygon functions ... | |
//------------------------------------------------------------------------------ | |
//------------------------------------------------------------------------------ | |
// SimplifyPolygon functions ... | |
// Convert self-intersecting polygons into simple polygons | |
//------------------------------------------------------------------------------ | |
public static Polygons SimplifyPolygon(Polygon poly) | |
{ | |
Polygons result = new Polygons(); | |
Clipper c = new Clipper(); | |
c.AddPolygon(poly, PolyType.Subject); | |
c.Execute(ClipType.Union, result); | |
return result; | |
} | |
//------------------------------------------------------------------------------ | |
public static Polygons SimplifyPolygons(Polygons polys) | |
{ | |
Polygons result = new Polygons(); | |
Clipper c = new Clipper(); | |
c.AddPolygons(polys, PolyType.Subject); | |
c.Execute(ClipType.Union, result); | |
return result; | |
} | |
//------------------------------------------------------------------------------ | |
} //end ClipperLib namespace | |
class ClipperException : Exception | |
{ | |
public ClipperException(string description) : base(description) { } | |
} | |
//------------------------------------------------------------------------------ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Contains basic data types used by Clipper and PolygonMath: | |
// - Int128 (128-bit integer) | |
// - PointL (Point made of two longs) | |
// - RectangleL (Rectangle made of four longs) | |
// - ExPolygon (a main polygon + holes) | |
// Note that the Polygon type is simply an alias for List<PointL>, | |
// and Polygons is simply an alias for List<List<PointL>>. | |
// | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
namespace ClipperLib | |
{ | |
using Polygon = List<PointL>; | |
using Polygons = List<List<PointL>>; | |
//------------------------------------------------------------------------------ | |
// Int128 class (enables safe math on signed 64bit integers) | |
// eg Int128 val1((long)9223372036854775807); //ie 2^63 -1 | |
// Int128 val2((long)9223372036854775807); | |
// Int128 val3 = val1 * val2; | |
// val3.ToString => "85070591730234615847396907784232501249" (8.5e+37) | |
//------------------------------------------------------------------------------ | |
internal struct Int128 | |
{ | |
private long hi; | |
private long lo; | |
public Int128(long _lo) | |
{ | |
lo = _lo; | |
hi = 0; | |
if (_lo < 0) | |
hi = -1; | |
} | |
public Int128(Int128 val) | |
{ | |
hi = val.hi; | |
lo = val.lo; | |
} | |
public Int128(long lo, long hi) | |
{ | |
this.lo = lo; | |
this.hi = hi; | |
} | |
public bool IsNegative() | |
{ | |
return hi < 0; | |
} | |
public static bool operator ==(Int128 val1, Int128 val2) | |
{ | |
if ((object)val1 == (object)val2) | |
return true; | |
else if ((object)val1 == null || (object)val2 == null) | |
return false; | |
return (val1.hi == val2.hi && val1.lo == val2.lo); | |
} | |
public static bool operator !=(Int128 val1, Int128 val2) | |
{ | |
return !(val1 == val2); | |
} | |
public override bool Equals(System.Object obj) | |
{ | |
if (obj == null || !(obj is Int128)) | |
return false; | |
Int128 i128 = (Int128)obj; | |
return (i128.hi == hi && i128.lo == lo); | |
} | |
public override int GetHashCode() | |
{ | |
return hi.GetHashCode() ^ lo.GetHashCode(); | |
} | |
public static bool operator >(Int128 val1, Int128 val2) | |
{ | |
if (System.Object.ReferenceEquals(val1, val2)) | |
return false; | |
else if (val2 == null) | |
return true; | |
else if (val1 == null) | |
return false; | |
else if (val1.hi > val2.hi) | |
return true; | |
else if (val1.hi < val2.hi) | |
return false; | |
else | |
return (ulong)val1.lo > (ulong)val2.lo; | |
} | |
public static bool operator <(Int128 val1, Int128 val2) | |
{ | |
if (System.Object.ReferenceEquals(val1, val2)) | |
return false; | |
else if (val2 == null) | |
return false; | |
else if (val1 == null) | |
return true; | |
if (val1.hi < val2.hi) | |
return true; | |
else if (val1.hi > val2.hi) | |
return false; | |
else | |
return (ulong)val1.lo < (ulong)val2.lo; | |
} | |
public static Int128 operator +(Int128 lhs, Int128 rhs) | |
{ | |
lhs.hi += rhs.hi; | |
lhs.lo += rhs.lo; | |
if ((ulong)lhs.lo < (ulong)rhs.lo) | |
lhs.hi++; | |
return lhs; | |
} | |
public static Int128 operator -(Int128 lhs, Int128 rhs) | |
{ | |
return lhs + -rhs; | |
} | |
public static Int128 operator -(Int128 val) | |
{ | |
if (val.lo == 0) { | |
if (val.hi == 0) | |
return val; | |
return new Int128(~val.lo, ~val.hi + 1); | |
} else { | |
return new Int128(~val.lo + 1, ~val.hi); | |
} | |
} | |
//nb: Constructing two new Int128 objects every time we want to multiply longs | |
//is slow. So, although calling the Mul method doesn't look as clean, the | |
//code runs significantly faster than if we'd used the * operator. | |
public static Int128 Mul(long lhs, long rhs) | |
{ | |
bool negate = (lhs < 0) != (rhs < 0); | |
if (lhs < 0) | |
lhs = -lhs; | |
if (rhs < 0) | |
rhs = -rhs; | |
ulong int1Hi = (ulong)lhs >> 32; | |
ulong int1Lo = (ulong)lhs & 0xFFFFFFFF; | |
ulong int2Hi = (ulong)rhs >> 32; | |
ulong int2Lo = (ulong)rhs & 0xFFFFFFFF; | |
//nb: see comments in clipper.pas | |
ulong a = int1Hi * int2Hi; | |
ulong b = int1Lo * int2Lo; | |
ulong c = int1Hi * int2Lo + int1Lo * int2Hi; | |
long lo, hi; | |
hi = (long)(a + (c >> 32)); | |
lo = (long)(c << 32); | |
lo += (long)b; | |
if ((ulong)lo < b) | |
hi++; | |
var result = new Int128(lo, hi); | |
return negate ? -result : result; | |
} | |
public static Int128 operator /(Int128 lhs, Int128 rhs) | |
{ | |
if (rhs.lo == 0 && rhs.hi == 0) | |
throw new ArithmeticException("Int128: divide by zero"); | |
bool negate = (rhs.hi < 0) != (lhs.hi < 0); | |
Int128 result = new Int128(lhs), denom = new Int128(rhs); | |
if (result.hi < 0) | |
result = -result; | |
if (denom.hi < 0) | |
denom = -denom; | |
if (denom > result) | |
return new Int128(0); //result is only a fraction of 1 | |
denom = -denom; | |
Int128 p = new Int128(0); | |
for (int i = 0; i < 128; ++i) { | |
p.hi = p.hi << 1; | |
if (p.lo < 0) | |
p.hi++; | |
p.lo = (long)p.lo << 1; | |
if (result.hi < 0) | |
p.lo++; | |
result.hi = result.hi << 1; | |
if (result.lo < 0) | |
result.hi++; | |
result.lo = (long)result.lo << 1; | |
if (p.hi >= 0) { | |
p += denom; | |
result.lo++; | |
} | |
} | |
if (negate) | |
return -result; | |
return result; | |
} | |
public double ToDouble() | |
{ | |
const double shift64 = 18446744073709551616.0; //2^64 | |
const double bit64 = 9223372036854775808.0; | |
if (hi < 0) { | |
Int128 tmp = -this; | |
if (tmp.lo < 0) | |
return (double)tmp.lo - bit64 - tmp.hi * shift64; | |
else | |
return -(double)tmp.lo - tmp.hi * shift64; | |
} else if (lo < 0) | |
return -(double)lo + bit64 + hi * shift64; | |
else | |
return (double)lo + (double)hi * shift64; | |
} | |
////for bug testing ... | |
//public override string ToString() | |
//{ | |
// int r = 0; | |
// Int128 tmp = new Int128(0), val = new Int128(this); | |
// if (hi < 0) Negate(val); | |
// StringBuilder builder = new StringBuilder(50); | |
// while (val.hi != 0 || val.lo != 0) | |
// { | |
// Div10(val, ref tmp, ref r); | |
// builder.Insert(0, (char)('0' + r)); | |
// val.Assign(tmp); | |
// } | |
// if (hi < 0) return '-' + builder.ToString(); | |
// if (builder.Length == 0) return "0"; | |
// return builder.ToString(); | |
//} | |
////debugging only ... | |
//private void Div10(Int128 val, ref Int128 result, ref int remainder) | |
//{ | |
// remainder = 0; | |
// result = new Int128(0); | |
// for (int i = 63; i >= 0; --i) | |
// { | |
// if ((val.hi & ((long)1 << i)) != 0) | |
// remainder = (remainder * 2) + 1; | |
// else | |
// remainder *= 2; | |
// if (remainder >= 10) | |
// { | |
// result.hi += ((long)1 << i); | |
// remainder -= 10; | |
// } | |
// } | |
// for (int i = 63; i >= 0; --i) | |
// { | |
// if ((val.lo & ((long)1 << i)) != 0) | |
// remainder = (remainder * 2) + 1; | |
// else | |
// remainder *= 2; | |
// if (remainder >= 10) | |
// { | |
// result.lo += ((long)1 << i); | |
// remainder -= 10; | |
// } | |
// } | |
//} | |
}; | |
//------------------------------------------------------------------------------ | |
//------------------------------------------------------------------------------ | |
public struct PointL | |
{ | |
public long X; | |
public long Y; | |
public PointL(long X, long Y) | |
{ | |
this.X = X; | |
this.Y = Y; | |
} | |
public PointL(PointL pt) | |
{ | |
this.X = pt.X; | |
this.Y = pt.Y; | |
} | |
public override bool Equals(object obj) | |
{ | |
if (!(obj is PointL)) | |
return false; | |
return (PointL)obj == this; | |
} | |
public override int GetHashCode() | |
{ | |
return X.GetHashCode() ^ Y.GetHashCode(); | |
} | |
public static bool operator== (PointL a, PointL b) | |
{ | |
return a.X == b.X && a.Y == b.Y; | |
} | |
public static bool operator !=(PointL a, PointL b) | |
{ | |
return a.X != b.X || a.Y != b.Y; | |
} | |
public override string ToString() | |
{ | |
return string.Format("({0},{1})", X, Y); | |
} | |
} | |
public class RectangleL | |
{ | |
public long left { get; set; } | |
public long top { get; set; } | |
public long right { get; set; } | |
public long bottom { get; set; } | |
public RectangleL(long l, long t, long r, long b) | |
{ | |
this.left = l; | |
this.top = t; | |
this.right = r; | |
this.bottom = b; | |
} | |
public RectangleL() | |
{ | |
this.left = 0; | |
this.top = 0; | |
this.right = 0; | |
this.bottom = 0; | |
} | |
} | |
public class ExPolygon | |
{ | |
public Polygon outer; | |
public Polygons holes; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
namespace ClipperLib | |
{ | |
using Polygon = List<PointL>; | |
using Polygons = List<List<PointL>>; | |
public enum JoinType { Square, Round, Miter }; | |
/// <summary> | |
/// Contains three useful polygon algorithms: Area, Orientation, and | |
/// OffsetPolygons. | |
/// </summary> | |
/// <remarks> | |
/// Clipper does not use PolygonMath, but PolygonMath.OffsetPolygons uses | |
/// Clipper to "clean up" the results of the offset operation. | |
/// </remarks> | |
public static class PolygonMath | |
{ | |
internal const long LoRange = 1518500249; //sqrt(2^63 -1)/2 | |
internal const long HiRange = 6521908912666391106L; //sqrt(2^127 -1)/2 | |
internal enum RangeTest { Lo, Hi, Error }; | |
#region Area | |
/// <summary>Computes the area of a polygon.</summary> | |
/// <returns>The area, which is always a multiple of 0.5 because the | |
/// input coordinates are integers. The result is negative if the polygon | |
/// is clockwise (assuming a coordinate system in which increasing Y goes | |
/// upward), or positive if the polygon is counterclockwise.</returns> | |
public static double Area(Polygon poly) | |
{ | |
int highI = poly.Count - 1; | |
if (highI < 2) | |
return 0; | |
bool UseFullInt64Range = false; | |
RangeTest rt = TestRange(poly); | |
switch (rt) { | |
case RangeTest.Hi: | |
UseFullInt64Range = true; | |
break; | |
case RangeTest.Error: | |
throw new ArgumentException("Polygon coordinate is too large to process."); | |
} | |
if (UseFullInt64Range) { | |
Int128 a = new Int128(0); | |
a = Int128.Mul(poly[highI].X, poly[0].Y) - | |
Int128.Mul(poly[0].X, poly[highI].Y); | |
for (int i = 0; i < highI; ++i) | |
a += Int128.Mul(poly[i].X, poly[i + 1].Y) - | |
Int128.Mul(poly[i + 1].X, poly[i].Y); | |
return a.ToDouble() / 2; | |
} else { | |
double area = (double)poly[highI].X * (double)poly[0].Y - | |
(double)poly[0].X * (double)poly[highI].Y; | |
for (int i = 0; i < highI; ++i) | |
area += (double)poly[i].X * (double)poly[i + 1].Y - | |
(double)poly[i + 1].X * (double)poly[i].Y; | |
return area / 2; | |
} | |
} | |
private static RangeTest TestRange(Polygon pts) | |
{ | |
RangeTest result = RangeTest.Lo; | |
for (int i = 0; i < pts.Count; i++) { | |
if (Math.Abs(pts[i].X) > HiRange || Math.Abs(pts[i].Y) > HiRange) | |
return RangeTest.Error; | |
else if (Math.Abs(pts[i].X) > LoRange || Math.Abs(pts[i].Y) > LoRange) | |
result = RangeTest.Hi; | |
} | |
return result; | |
} | |
#endregion | |
#region Orientation | |
/// <summary>Computes the orientation of a polygon.</summary> | |
/// <returns>A return value of "true" represents a clockwise polygon if | |
/// you are using a "mathematical" coordinate system in which increasing | |
/// Y coordinates move upward, so "false" represents a counterclockwise | |
/// polygon. However, if you are using a text-oriented coordinate system | |
/// in which increasing Y coordinates move downward (in that case, (0,0) is | |
/// typically the top-left corner), then "true" represents a | |
/// counterclockwise polygon.</returns> | |
public static bool Orientation(Polygon poly) | |
{ | |
int highI = poly.Count - 1; | |
if (highI < 2) | |
return false; | |
bool UseFullInt64Range = false; | |
int j = 0, jplus, jminus; | |
for (int i = 0; i <= highI; ++i) { | |
if (Math.Abs(poly[i].X) > HiRange || Math.Abs(poly[i].Y) > HiRange) | |
throw new ArgumentException("Polygon coordinate is too large to process."); | |
if (Math.Abs(poly[i].X) > LoRange || Math.Abs(poly[i].Y) > LoRange) | |
UseFullInt64Range = true; | |
if (poly[i].Y < poly[j].Y) | |
continue; | |
if ((poly[i].Y > poly[j].Y || poly[i].X < poly[j].X)) | |
j = i; | |
}; | |
if (j == highI) | |
jplus = 0; | |
else | |
jplus = j + 1; | |
if (j == 0) | |
jminus = highI; | |
else | |
jminus = j - 1; | |
//get cross product of vectors of the edges adjacent to highest point ... | |
PointL vec1 = new PointL(poly[j].X - poly[jminus].X, poly[j].Y - poly[jminus].Y); | |
PointL vec2 = new PointL(poly[jplus].X - poly[j].X, poly[jplus].Y - poly[j].Y); | |
if (UseFullInt64Range) { | |
Int128 cross = Int128.Mul(vec1.X, vec2.Y) - Int128.Mul(vec2.X, vec1.Y); | |
return cross.IsNegative(); | |
} else { | |
return (vec1.X * vec2.Y - vec2.X * vec1.Y) < 0; | |
} | |
} | |
#endregion | |
#region OffsetPolygons | |
internal static long Round(double value) | |
{ | |
if ((value < 0)) | |
return (long)(value - 0.5); | |
else | |
return (long)(value + 0.5); | |
} | |
internal struct DoublePoint | |
{ | |
public readonly double X; | |
public readonly double Y; | |
public DoublePoint(double x = 0, double y = 0) | |
{ | |
this.X = x; | |
this.Y = y; | |
} | |
}; | |
private class PolyOffsetBuilder | |
{ | |
private Polygons pts; | |
private Polygon currentPoly; | |
private List<DoublePoint> normals; | |
private double delta, m_R; | |
private int m_i, m_j, m_k; | |
private const int buffLength = 128; | |
public PolyOffsetBuilder(Polygons pts, Polygons solution, double delta, JoinType jointype, double MiterLimit) | |
{ | |
//precondtion: solution != pts | |
if (delta == 0) { | |
solution = pts; | |
return; | |
} | |
this.pts = pts; | |
this.delta = delta; | |
if (MiterLimit <= 1) | |
MiterLimit = 1; | |
double RMin = 2 / (MiterLimit * MiterLimit); | |
normals = new List<DoublePoint>(); | |
double deltaSq = delta * delta; | |
solution.Clear(); | |
solution.Capacity = pts.Count; | |
for (m_i = 0; m_i < pts.Count; m_i++) { | |
int len = pts[m_i].Count; | |
if (len > 1 && pts[m_i][0].X == pts[m_i][len - 1].X && | |
pts[m_i][0].Y == pts[m_i][len - 1].Y) | |
len--; | |
if (len == 0 || (len < 3 && delta <= 0)) | |
continue; | |
else if (len == 1) { | |
Polygon arc; | |
arc = BuildArc(pts[m_i][len - 1], 0, 2 * Math.PI, delta); | |
solution.Add(arc); | |
continue; | |
} | |
//build normals ... | |
normals.Clear(); | |
normals.Capacity = len; | |
for (int j = 0; j < len - 1; ++j) | |
normals.Add(GetUnitNormal(pts[m_i][j], pts[m_i][j + 1])); | |
normals.Add(GetUnitNormal(pts[m_i][len - 1], pts[m_i][0])); | |
currentPoly = new Polygon(); | |
m_k = len - 1; | |
for (m_j = 0; m_j < len; ++m_j) { | |
switch (jointype) { | |
case JoinType.Miter: { | |
m_R = 1 + (normals[m_j].X * normals[m_k].X + | |
normals[m_j].Y * normals[m_k].Y); | |
if (m_R >= RMin) | |
DoMiter(); | |
else | |
DoSquare(MiterLimit); | |
break; | |
} | |
case JoinType.Round: | |
DoRound(); | |
break; | |
case JoinType.Square: | |
DoSquare(1); | |
break; | |
} | |
m_k = m_j; | |
} | |
solution.Add(currentPoly); | |
} | |
//finally, clean up untidy corners ... | |
CleanUpSolution(solution, delta); | |
} | |
private static void CleanUpSolution(Polygons solution, double delta) | |
{ | |
Clipper clpr = new Clipper(); | |
clpr.AddPolygons(solution, PolyType.Subject); | |
if (delta > 0) { | |
clpr.Execute(ClipType.Union, solution, PolyFillType.Positive, PolyFillType.Positive); | |
} else { | |
RectangleL r = clpr.GetBounds(); | |
Polygon outer = new Polygon(4); | |
outer.Add(new PointL(r.left - 10, r.bottom + 10)); | |
outer.Add(new PointL(r.right + 10, r.bottom + 10)); | |
outer.Add(new PointL(r.right + 10, r.top - 10)); | |
outer.Add(new PointL(r.left - 10, r.top - 10)); | |
clpr.AddPolygon(outer, PolyType.Subject); | |
clpr.Execute(ClipType.Union, solution, PolyFillType.Negative, PolyFillType.Negative); | |
if (solution.Count > 0) { | |
solution.RemoveAt(0); | |
for (int i = 0; i < solution.Count; i++) | |
solution[i].Reverse(); | |
} | |
} | |
} | |
//------------------------------------------------------------------------------ | |
internal void AddPoint(PointL pt) | |
{ | |
int len = currentPoly.Count; | |
if (len == currentPoly.Capacity) | |
currentPoly.Capacity = len + buffLength; | |
currentPoly.Add(pt); | |
} | |
//------------------------------------------------------------------------------ | |
internal void DoSquare(double mul) | |
{ | |
PointL pt1 = new PointL((long)Round(pts[m_i][m_j].X + normals[m_k].X * delta), | |
(long)Round(pts[m_i][m_j].Y + normals[m_k].Y * delta)); | |
PointL pt2 = new PointL((long)Round(pts[m_i][m_j].X + normals[m_j].X * delta), | |
(long)Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); | |
if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * delta >= 0) { | |
double a1 = Math.Atan2(normals[m_k].Y, normals[m_k].X); | |
double a2 = Math.Atan2(-normals[m_j].Y, -normals[m_j].X); | |
a1 = Math.Abs(a2 - a1); | |
if (a1 > Math.PI) | |
a1 = Math.PI * 2 - a1; | |
double dx = Math.Tan((Math.PI - a1) / 4) * Math.Abs(delta * mul); | |
pt1 = new PointL((long)(pt1.X - normals[m_k].Y * dx), | |
(long)(pt1.Y + normals[m_k].X * dx)); | |
AddPoint(pt1); | |
pt2 = new PointL((long)(pt2.X + normals[m_j].Y * dx), | |
(long)(pt2.Y - normals[m_j].X * dx)); | |
AddPoint(pt2); | |
} else { | |
AddPoint(pt1); | |
AddPoint(pts[m_i][m_j]); | |
AddPoint(pt2); | |
} | |
} | |
//------------------------------------------------------------------------------ | |
internal void DoMiter() | |
{ | |
if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * delta >= 0) { | |
double q = delta / m_R; | |
AddPoint(new PointL((long)Round(pts[m_i][m_j].X + | |
(normals[m_k].X + normals[m_j].X) * q), | |
(long)Round(pts[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q))); | |
} else { | |
PointL pt1 = new PointL((long)Round(pts[m_i][m_j].X + normals[m_k].X * delta), | |
(long)Round(pts[m_i][m_j].Y + normals[m_k].Y * delta)); | |
PointL pt2 = new PointL((long)Round(pts[m_i][m_j].X + normals[m_j].X * delta), | |
(long)Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); | |
AddPoint(pt1); | |
AddPoint(pts[m_i][m_j]); | |
AddPoint(pt2); | |
} | |
} | |
//------------------------------------------------------------------------------ | |
internal void DoRound() | |
{ | |
PointL pt1 = new PointL(Round(pts[m_i][m_j].X + normals[m_k].X * delta), | |
Round(pts[m_i][m_j].Y + normals[m_k].Y * delta)); | |
PointL pt2 = new PointL(Round(pts[m_i][m_j].X + normals[m_j].X * delta), | |
Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); | |
AddPoint(pt1); | |
//round off reflex angles (ie > 180 deg) unless almost flat (ie < 10deg). | |
//cross product normals < 0 . angle > 180 deg. | |
//dot product normals == 1 . no angle | |
if ((normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y) * delta >= 0) { | |
if ((normals[m_j].X * normals[m_k].X + normals[m_j].Y * normals[m_k].Y) < 0.985) { | |
double a1 = Math.Atan2(normals[m_k].Y, normals[m_k].X); | |
double a2 = Math.Atan2(normals[m_j].Y, normals[m_j].X); | |
if (delta > 0 && a2 < a1) | |
a2 += Math.PI * 2; | |
else if (delta < 0 && a2 > a1) | |
a2 -= Math.PI * 2; | |
Polygon arc = BuildArc(pts[m_i][m_j], a1, a2, delta); | |
for (int m = 0; m < arc.Count; m++) | |
AddPoint(arc[m]); | |
} | |
} else | |
AddPoint(pts[m_i][m_j]); | |
AddPoint(pt2); | |
} | |
//------------------------------------------------------------------------------ | |
} //end PolyOffsetBuilder | |
/// <summary>Encapsulates the algorithm for computing the offsets of a | |
/// polygon. You can either add a positive offset (creating a buffer zone | |
/// around the input) or a negative one (shrinking the input polygons).</summary> | |
public static Polygons OffsetPolygons(Polygons poly, double delta, | |
JoinType jointype, double MiterLimit) | |
{ | |
Polygons result = new Polygons(poly.Count); | |
new PolyOffsetBuilder(poly, result, delta, jointype, MiterLimit); | |
return result; | |
} | |
/// <summary>Encapsulates the algorithm for computing the offsets of a | |
/// polygon. You can either add a positive offset (creating a buffer zone | |
/// around the input) or a negative one (shrinking the input polygons).</summary> | |
public static Polygons OffsetPolygons(Polygons poly, double delta, JoinType jointype) | |
{ | |
Polygons result = new Polygons(poly.Count); | |
new PolyOffsetBuilder(poly, result, delta, jointype, 2.0); | |
return result; | |
} | |
/// <summary>Encapsulates the algorithm for computing the offsets of a | |
/// polygon. You can either add a positive offset (creating a buffer zone | |
/// around the input) or a negative one (shrinking the input polygons).</summary> | |
public static Polygons OffsetPolygons(Polygons poly, double delta) | |
{ | |
Polygons result = new Polygons(poly.Count); | |
new PolyOffsetBuilder(poly, result, delta, JoinType.Square, 2.0); | |
return result; | |
} | |
internal static DoublePoint GetUnitNormal(PointL pt1, PointL pt2) | |
{ | |
double dx = (pt2.X - pt1.X); | |
double dy = (pt2.Y - pt1.Y); | |
if ((dx == 0) && (dy == 0)) | |
return new DoublePoint(); | |
double f = 1.0 / Math.Sqrt(dx * dx + dy * dy); | |
dx *= f; | |
dy *= f; | |
return new DoublePoint(dy, -dx); | |
} | |
//------------------------------------------------------------------------------ | |
internal static Polygon BuildArc(PointL pt, double a1, double a2, double r) | |
{ | |
int steps = Math.Max(6, (int)(Math.Sqrt(Math.Abs(r)) * Math.Abs(a2 - a1))); | |
Polygon result = new Polygon(steps); | |
int n = steps - 1; | |
double da = (a2 - a1) / n; | |
double a = a1; | |
for (int i = 0; i < steps; ++i) { | |
result.Add(new PointL(pt.X + Round(Math.Cos(a) * r), pt.Y + Round(Math.Sin(a) * r))); | |
a += da; | |
} | |
return result; | |
} | |
//------------------------------------------------------------------------------ | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment