package org.geogebra.common.util.clipper; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.util.clipper.Point.DoublePoint; public class DefaultClipper extends ClipperBase { private static class IntersectNode implements Comparator<IntersectNode> { protected IntersectNode() { // avoid synth access warning } Edge edge1; Edge Edge2; // private LongPoint pt; private DoublePoint pt; /** * modified to be compatible with double */ public DoublePoint getPt() { return pt; } public void setPt(DoublePoint pt) { this.pt = pt; } @Override public int compare(IntersectNode node1, IntersectNode node2) { // final long i = node2.getPt().getY() - node1.getPt().getY(); final double i = node2.getPt().getY() - node1.getPt().getY(); return (int) Math.signum(i); // if (i > 0) { // return 1; // } else if (i < 0) { // return -1; // } else { // return 0; // } } } /** * modified to be compatible with double */ private static void getHorzDirection(Edge HorzEdge, Direction[] Dir, double[] Left, double[] Right) { if (HorzEdge.getBot().getX() < HorzEdge.getTop().getX()) { Left[0] = HorzEdge.getBot().getX(); Right[0] = HorzEdge.getTop().getX(); Dir[0] = Direction.LEFT_TO_RIGHT; } else { Left[0] = HorzEdge.getTop().getX(); Right[0] = HorzEdge.getBot().getX(); Dir[0] = Direction.RIGHT_TO_LEFT; } } /** * modified to be compatible with double */ private static boolean getOverlap(double a1, double a2, double b1, double b2, double[] Left, double[] Right) { if (a1 < a2) { if (b1 < b2) { Left[0] = Math.max(a1, b1); Right[0] = Math.min(a2, b2); } else { Left[0] = Math.max(a1, b2); Right[0] = Math.min(a2, b1); } } else { if (b1 < b2) { Left[0] = Math.max(a2, b1); Right[0] = Math.min(a1, b2); } else { Left[0] = Math.max(a2, b2); Right[0] = Math.min(a1, b1); } } return Left[0] < Right[0]; } private static boolean isParam1RightOfParam2(OutRec outRec0, OutRec outRec2) { OutRec outRec1 = outRec0; do { outRec1 = outRec1.firstLeft; if (outRec1 == outRec2) { return true; } } while (outRec1 != null); return false; } /** * modified to be compatible with double */ private static int isPointInPolygon(DoublePoint pt, final OutPt startOp) { // returns 0 if false, +1 if true, -1 if pt ON polygon boundary // See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann // & Agathos // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf int result = 0; OutPt op = startOp; final double ptx = pt.getX(), pty = pt.getY(); double poly0x = op.getPt().getX(), poly0y = op.getPt().getY(); do { op = op.next; final double poly1x = op.getPt().getX(), poly1y = op.getPt().getY(); if (poly1y == pty) { if (poly1x == ptx || poly0y == pty && poly1x > ptx == poly0x < ptx) { return -1; } } if (poly0y < pty != poly1y < pty) { if (poly0x >= ptx) { if (poly1x > ptx) { result = 1 - result; } else { final double d = (poly0x - ptx) * (poly1y - pty) - (poly1x - ptx) * (poly0y - pty); if (d == 0) { return -1; } if (d > 0 == poly1y > poly0y) { result = 1 - result; } } } else { if (poly1x > ptx) { final double d = (poly0x - ptx) * (poly1y - pty) - (poly1x - ptx) * (poly0y - pty); if (d == 0) { return -1; } if (d > 0 == poly1y > poly0y) { result = 1 - result; } } } } poly0x = poly1x; poly0y = poly1y; } while (startOp != op); return result; } // ------------------------------------------------------------------------------ /** * modified to be compatible with double */ private static boolean joinHorz(OutPt opStart, OutPt opStartB, OutPt opEnd, OutPt opEndB, DoublePoint Pt, boolean DiscardLeft) { OutPt op1 = opStart; OutPt op1b = opStartB; OutPt op2 = opEnd; OutPt op2b = opEndB; final Direction Dir1 = op1.getPt().getX() > op1b.getPt().getX() ? Direction.RIGHT_TO_LEFT : Direction.LEFT_TO_RIGHT; final Direction Dir2 = op2.getPt().getX() > op2b.getPt().getX() ? Direction.RIGHT_TO_LEFT : Direction.LEFT_TO_RIGHT; if (Dir1 == Dir2) { return false; } // When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we // want Op1b to be on the Right. (And likewise with Op2 and Op2b.) // So, to facilitate this while inserting Op1b and Op2b ... // when DiscardLeft, make sure we're AT or RIGHT of Pt before adding // Op1b, // otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) if (Dir1 == Direction.LEFT_TO_RIGHT) { while (op1.next.getPt().getX() <= Pt.getX() && op1.next.getPt().getX() >= op1.getPt().getX() && op1.next.getPt().getY() == Pt.getY()) { op1 = op1.next; } if (DiscardLeft && op1.getPt().getX() != Pt.getX()) { op1 = op1.next; } op1b = op1.duplicate(!DiscardLeft); if (!op1b.getPt().equals(Pt)) { op1 = op1b; op1.setPt(new DoublePoint(Pt)); op1b = op1.duplicate(!DiscardLeft); } } else { while (op1.next.getPt().getX() >= Pt.getX() && op1.next.getPt().getX() <= op1.getPt().getX() && op1.next.getPt().getY() == Pt.getY()) { op1 = op1.next; } if (!DiscardLeft && op1.getPt().getX() != Pt.getX()) { op1 = op1.next; } op1b = op1.duplicate(DiscardLeft); if (!op1b.getPt().equals(Pt)) { op1 = op1b; op1.setPt(new DoublePoint(Pt)); op1b = op1.duplicate(DiscardLeft); } } if (Dir2 == Direction.LEFT_TO_RIGHT) { while (op2.next.getPt().getX() <= Pt.getX() && op2.next.getPt().getX() >= op2.getPt().getX() && op2.next.getPt().getY() == Pt.getY()) { op2 = op2.next; } if (DiscardLeft && op2.getPt().getX() != Pt.getX()) { op2 = op2.next; } op2b = op2.duplicate(!DiscardLeft); if (!op2b.getPt().equals(Pt)) { op2 = op2b; op2.setPt(new DoublePoint(Pt)); op2b = op2.duplicate(!DiscardLeft); } } else { while (op2.next.getPt().getX() >= Pt.getX() && op2.next.getPt().getX() <= op2.getPt().getX() && op2.next.getPt().getY() == Pt.getY()) { op2 = op2.next; } if (!DiscardLeft && op2.getPt().getX() != Pt.getX()) { op2 = op2.next; } op2b = op2.duplicate(DiscardLeft); if (!op2b.getPt().equals(Pt)) { op2 = op2b; op2.setPt(new DoublePoint(Pt)); op2b = op2.duplicate(DiscardLeft); } } if (Dir1 == Direction.LEFT_TO_RIGHT == DiscardLeft) { op1.prev = op2; op2.next = op1; op1b.next = op2b; op2b.prev = op1b; } else { op1.next = op2; op2.prev = op1; op1b.prev = op2b; op2b.next = op1b; } return true; } /** * modified to be compatible with double */ private static boolean joinPoints(Join j, OutRec outRec1, OutRec outRec2) { OutPt op1 = j.outPt1, op1b; OutPt op2 = j.outPt2, op2b; // There are 3 kinds of joins for output polygons ... // 1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are a vertices // anywhere // along (horizontal) collinear edges (& Join.OffPt is on the same // horizontal). // 2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the // same // location at the Bottom of the overlapping segment (& Join.OffPt is // above). // 3. StrictlySimple joins where edges touch but are not collinear and // where // Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. final boolean isHorizontal = j.outPt1.getPt().getY() == j.getOffPt() .getY(); if (isHorizontal && j.getOffPt().equals(j.outPt1.getPt()) && j.getOffPt().equals(j.outPt2.getPt())) { // Strictly Simple join ... if (outRec1 != outRec2) { return false; } op1b = j.outPt1.next; while (op1b != op1 && op1b.getPt().equals(j.getOffPt())) { op1b = op1b.next; } final boolean reverse1 = op1b.getPt().getY() > j.getOffPt().getY(); op2b = j.outPt2.next; while (op2b != op2 && op2b.getPt().equals(j.getOffPt())) { op2b = op2b.next; } final boolean reverse2 = op2b.getPt().getY() > j.getOffPt().getY(); if (reverse1 == reverse2) { return false; } if (reverse1) { op1b = op1.duplicate(false); op2b = op2.duplicate(true); op1.prev = op2; op2.next = op1; op1b.next = op2b; op2b.prev = op1b; j.outPt1 = op1; j.outPt2 = op1b; return true; } op1b = op1.duplicate(true); op2b = op2.duplicate(false); op1.next = op2; op2.prev = op1; op1b.prev = op2b; op2b.next = op1b; j.outPt1 = op1; j.outPt2 = op1b; return true; } else if (isHorizontal) { // treat horizontal joins differently to non-horizontal joins since // with // them we're not yet sure where the overlapping is. OutPt1.Pt & // OutPt2.Pt // may be anywhere along the horizontal edge. op1b = op1; while (op1.prev.getPt().getY() == op1.getPt().getY() && op1.prev != op1b && op1.prev != op2) { op1 = op1.prev; } while (op1b.next.getPt().getY() == op1b.getPt().getY() && op1b.next != op1 && op1b.next != op2) { op1b = op1b.next; } if (op1b.next == op1 || op1b.next == op2) { return false; } // a flat 'polygon' op2b = op2; while (op2.prev.getPt().getY() == op2.getPt().getY() && op2.prev != op2b && op2.prev != op1b) { op2 = op2.prev; } while (op2b.next.getPt().getY() == op2b.getPt().getY() && op2b.next != op2 && op2b.next != op1) { op2b = op2b.next; } if (op2b.next == op2 || op2b.next == op1) { return false; } // a flat 'polygon' final double[] LeftV = new double[1], RightV = new double[1]; // Op1 -. Op1b & Op2 -. Op2b are the extremites of the horizontal // edges if (!getOverlap(op1.getPt().getX(), op1b.getPt().getX(), op2.getPt().getX(), op2b.getPt().getX(), LeftV, RightV)) { return false; } final double Left = LeftV[0]; final double Right = RightV[0]; // DiscardLeftSide: when overlapping edges are joined, a spike will // created // which needs to be cleaned up. However, we don't want Op1 or Op2 // caught up // on the discard Side as either may still be needed for other joins // ... DoublePoint Pt; boolean DiscardLeftSide; if (op1.getPt().getX() >= Left && op1.getPt().getX() <= Right) { Pt = new DoublePoint(op1.getPt()); DiscardLeftSide = op1.getPt().getX() > op1b.getPt().getX(); } else if (op2.getPt().getX() >= Left && op2.getPt().getX() <= Right) { Pt = new DoublePoint(op2.getPt()); DiscardLeftSide = op2.getPt().getX() > op2b.getPt().getX(); } else if (op1b.getPt().getX() >= Left && op1b.getPt().getX() <= Right) { Pt = new DoublePoint(op1b.getPt()); DiscardLeftSide = op1b.getPt().getX() > op1.getPt().getX(); } else { Pt = new DoublePoint(op2b.getPt()); DiscardLeftSide = op2b.getPt().getX() > op2.getPt().getX(); } j.outPt1 = op1; j.outPt2 = op2; return joinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); } else { // nb: For non-horizontal joins ... // 1. Jr.OutPt1.getPt().getY() == Jr.OutPt2.getPt().getY() // 2. Jr.OutPt1.Pt > Jr.OffPt.getY() // make sure the polygons are correctly oriented ... op1b = op1.next; while (op1b.getPt().equals(op1.getPt()) && op1b != op1) { op1b = op1b.next; } final boolean Reverse1 = op1b.getPt().getY() > op1.getPt().getY() || !Point.slopesEqual(op1.getPt(), op1b.getPt(), j.getOffPt()); if (Reverse1) { op1b = op1.prev; while (op1b.getPt().equals(op1.getPt()) && op1b != op1) { op1b = op1b.prev; } if (op1b.getPt().getY() > op1.getPt().getY() || !Point .slopesEqual(op1.getPt(), op1b.getPt(), j.getOffPt())) { return false; } } op2b = op2.next; while (op2b.getPt().equals(op2.getPt()) && op2b != op2) { op2b = op2b.next; } final boolean Reverse2 = op2b.getPt().getY() > op2.getPt().getY() || !Point.slopesEqual(op2.getPt(), op2b.getPt(), j.getOffPt()); if (Reverse2) { op2b = op2.prev; while (op2b.getPt().equals(op2.getPt()) && op2b != op2) { op2b = op2b.prev; } if (op2b.getPt().getY() > op2.getPt().getY() || !Point .slopesEqual(op2.getPt(), op2b.getPt(), j.getOffPt())) { return false; } } if (op1b == op1 || op2b == op2 || op1b == op2b || outRec1 == outRec2 && Reverse1 == Reverse2) { return false; } if (Reverse1) { op1b = op1.duplicate(false); op2b = op2.duplicate(true); op1.prev = op2; op2.next = op1; op1b.next = op2b; op2b.prev = op1b; j.outPt1 = op1; j.outPt2 = op1b; return true; } op1b = op1.duplicate(true); op2b = op2.duplicate(false); op1.next = op2; op2.prev = op1; op1b.prev = op2b; op2b.next = op1b; j.outPt1 = op1; j.outPt2 = op1b; return true; } } /** * modified to be compatible with double */ private static Paths minkowski(Path pattern, Path path, boolean IsSum, boolean IsClosed) { final int delta = IsClosed ? 1 : 0; final int polyCnt = pattern.size(); final int pathCnt = path.size(); final Paths result = new Paths(pathCnt); if (IsSum) { for (int i = 0; i < pathCnt; i++) { final Path p = new Path(polyCnt); for (final DoublePoint ip : pattern) { p.add(new DoublePoint(path.get(i).getX() + ip.getX(), path.get(i).getY() + ip.getY(), 0)); } result.add(p); } } else { for (int i = 0; i < pathCnt; i++) { final Path p = new Path(polyCnt); for (final DoublePoint ip : pattern) { p.add(new DoublePoint(path.get(i).getX() - ip.getX(), path.get(i).getY() - ip.getY(), 0)); } result.add(p); } } final Paths quads = new Paths((pathCnt + delta) * (polyCnt + 1)); for (int i = 0; i < pathCnt - 1 + delta; i++) { for (int j = 0; j < polyCnt; j++) { final Path quad = new Path(4); quad.add(result.get(i % pathCnt).get(j % polyCnt)); quad.add(result.get((i + 1) % pathCnt).get(j % polyCnt)); quad.add(result.get((i + 1) % pathCnt).get((j + 1) % polyCnt)); quad.add(result.get(i % pathCnt).get((j + 1) % polyCnt)); if (!quad.orientation()) { Collections.reverse(quad); } quads.add(quad); } } return quads; } public static Paths minkowskiDiff(Path poly1, Path poly2) { final Paths paths = minkowski(poly1, poly2, false, true); final DefaultClipper c = new DefaultClipper(); c.addPaths(paths, PolyType.SUBJECT, true); c.execute(ClipType.UNION, paths, PolyFillType.NON_ZERO, PolyFillType.NON_ZERO); return paths; } public static Paths minkowskiSum(Path pattern, Path path, boolean pathIsClosed) { final Paths paths = minkowski(pattern, path, true, pathIsClosed); final DefaultClipper c = new DefaultClipper(); c.addPaths(paths, PolyType.SUBJECT, true); c.execute(ClipType.UNION, paths, PolyFillType.NON_ZERO, PolyFillType.NON_ZERO); return paths; } public static Paths minkowskiSum(Path pattern, Paths paths, boolean pathIsClosed) { final Paths solution = new Paths(); final DefaultClipper c = new DefaultClipper(); for (int i = 0; i < paths.size(); ++i) { final Paths tmp = minkowski(pattern, paths.get(i), true, pathIsClosed); c.addPaths(tmp, PolyType.SUBJECT, true); if (pathIsClosed) { final Path path = paths.get(i).translatePath(pattern.get(0)); c.addPath(path, PolyType.CLIP, true); } } c.execute(ClipType.UNION, solution, PolyFillType.NON_ZERO, PolyFillType.NON_ZERO); return solution; } private static boolean poly2ContainsPoly1(OutPt outPt1, OutPt outPt2) { OutPt op = outPt1; do { // nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on // polygon final int res = isPointInPolygon(op.getPt(), outPt2); if (res >= 0) { return res > 0; } op = op.next; } while (op != outPt1); return true; } // ------------------------------------------------------------------------------ // SimplifyPolygon functions ... // Convert self-intersecting polygons into simple polygons // ------------------------------------------------------------------------------ public static Paths simplifyPolygon(Path poly) { return simplifyPolygon(poly, PolyFillType.EVEN_ODD); } public static Paths simplifyPolygon(Path poly, PolyFillType fillType) { final Paths result = new Paths(); final DefaultClipper c = new DefaultClipper(STRICTLY_SIMPLE); c.addPath(poly, PolyType.SUBJECT, true); c.execute(ClipType.UNION, result, fillType, fillType); return result; } public static Paths simplifyPolygons(Paths polys) { return simplifyPolygons(polys, PolyFillType.EVEN_ODD); } public static Paths simplifyPolygons(Paths polys, PolyFillType fillType) { final Paths result = new Paths(); final DefaultClipper c = new DefaultClipper(STRICTLY_SIMPLE); c.addPaths(polys, PolyType.SUBJECT, true); c.execute(ClipType.UNION, result, fillType, fillType); return result; } private final List<OutRec> polyOuts; private ClipType clipType; private Scanbeam scanbeam; private Edge activeEdges; private Edge sortedEdges; private final List<IntersectNode> intersectList; private PolyFillType clipFillType; // ------------------------------------------------------------------------------ private PolyFillType subjFillType; // ------------------------------------------------------------------------------ private final List<Join> joins; // ------------------------------------------------------------------------------ private final List<Join> ghostJoins; private boolean usingPolyTree; // public ZFillCallback zFillFunction; // ------------------------------------------------------------------------------ private final boolean reverseSolution; // ------------------------------------------------------------------------------ private final boolean strictlySimple; public DefaultClipper() { this(0); } public DefaultClipper(int InitOptions) // constructor { super((PRESERVE_COLINEAR & InitOptions) != 0); scanbeam = null; activeEdges = null; sortedEdges = null; intersectList = new ArrayList<IntersectNode>(); usingPolyTree = false; polyOuts = new ArrayList<OutRec>(); joins = new ArrayList<Join>(); ghostJoins = new ArrayList<Join>(); reverseSolution = (REVERSE_SOLUTION & InitOptions) != 0; strictlySimple = (STRICTLY_SIMPLE & InitOptions) != 0; // zFillFunction = null; } private void addEdgeToSEL(Edge 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 (sortedEdges == null) { sortedEdges = edge; edge.prevInSEL = null; edge.nextInSEL = null; } else { edge.nextInSEL = sortedEdges; edge.prevInSEL = null; sortedEdges.prevInSEL = edge; sortedEdges = edge; } } /** * modified to be compatible with double */ private void addGhostJoin(OutPt Op, DoublePoint OffPt) { final Join j = new Join(); j.outPt1 = Op; j.setOffPt(new DoublePoint(OffPt)); ghostJoins.add(j); } // ------------------------------------------------------------------------------ /** * modified to be compatible with double */ private void addJoin(OutPt Op1, OutPt Op2, DoublePoint OffPt) { final Join j = new Join(); j.outPt1 = Op1; j.outPt2 = Op2; j.setOffPt(new DoublePoint(OffPt)); joins.add(j); } // ------------------------------------------------------------------------------ /** * modified to be compatible with double */ private void addLocalMaxPoly(Edge e1, Edge e2, DoublePoint pt) { addOutPt(e1, pt); if (e2.windDelta == 0) { addOutPt(e2, pt); } if (e1.outIdx == e2.outIdx) { e1.outIdx = Edge.UNASSIGNED; e2.outIdx = Edge.UNASSIGNED; } else if (e1.outIdx < e2.outIdx) { appendPolygon(e1, e2); } else { appendPolygon(e2, e1); } } // ------------------------------------------------------------------------------ /** * modified to be compatible with double */ private OutPt addLocalMinPoly(Edge e1, Edge e2, DoublePoint pt) { OutPt result; Edge e, prevE; if (e2.isHorizontal() || e1.deltaX > e2.deltaX) { result = addOutPt(e1, pt); e2.outIdx = e1.outIdx; e1.side = Edge.Side.LEFT; e2.side = Edge.Side.RIGHT; e = e1; if (e.prevInAEL == e2) { prevE = e2.prevInAEL; } else { prevE = e.prevInAEL; } } else { result = addOutPt(e2, pt); e1.outIdx = e2.outIdx; e1.side = Edge.Side.RIGHT; e2.side = Edge.Side.LEFT; e = e2; if (e.prevInAEL == e1) { prevE = e1.prevInAEL; } else { prevE = e.prevInAEL; } } if (prevE != null && prevE.outIdx >= 0 && Edge.topX(prevE, pt.getY()) == Edge.topX(e, pt.getY()) && Edge.slopesEqual(e, prevE) && e.windDelta != 0 && prevE.windDelta != 0) { final OutPt outPt = addOutPt(prevE, pt); addJoin(result, outPt, e.getTop()); } return result; } /** * modified to be compatible with double */ private OutPt addOutPt(Edge e, DoublePoint pt) { final boolean ToFront = e.side == Edge.Side.LEFT; if (e.outIdx < 0) { final OutRec outRec = createOutRec(); outRec.isOpen = e.windDelta == 0; final OutPt newOp = new OutPt(); outRec.setPoints(newOp); newOp.idx = outRec.Idx; newOp.setPt(new DoublePoint(pt)); newOp.next = newOp; newOp.prev = newOp; if (!outRec.isOpen) { setHoleState(e, outRec); } e.outIdx = outRec.Idx; // nb: do this after SetZ ! return newOp; } final OutRec outRec = polyOuts.get(e.outIdx); // OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the // 'Right-most' final OutPt op = outRec.getPoints(); if (ToFront && pt.equals(op.getPt())) { return op; } else if (!ToFront && pt.equals(op.prev.getPt())) { return op.prev; } final OutPt newOp = new OutPt(); newOp.idx = outRec.Idx; newOp.setPt(new DoublePoint(pt)); newOp.next = op; newOp.prev = op.prev; newOp.prev.next = newOp; op.prev = newOp; if (ToFront) { outRec.setPoints(newOp); } return newOp; } private void appendPolygon(Edge e1, Edge e2) { // get the start and ends of both output polygons ... final OutRec outRec1 = polyOuts.get(e1.outIdx); final OutRec outRec2 = polyOuts.get(e2.outIdx); OutRec holeStateRec; if (isParam1RightOfParam2(outRec1, outRec2)) { holeStateRec = outRec2; } else if (isParam1RightOfParam2(outRec2, outRec1)) { holeStateRec = outRec1; } else { holeStateRec = OutPt.getLowerMostRec(outRec1, outRec2); } final OutPt p1_lft = outRec1.getPoints(); final OutPt p1_rt = p1_lft.prev; final OutPt p2_lft = outRec2.getPoints(); final OutPt p2_rt = p2_lft.prev; Edge.Side side; // join e2 poly onto e1 poly and delete pointers to e2 ... if (e1.side == Edge.Side.LEFT) { if (e2.side == Edge.Side.LEFT) { // z y x a b c p2_lft.reversePolyPtLinks(); p2_lft.next = p1_lft; p1_lft.prev = p2_lft; p1_rt.next = p2_rt; p2_rt.prev = p1_rt; outRec1.setPoints(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.setPoints(p2_lft); } side = Edge.Side.LEFT; } else { if (e2.side == Edge.Side.RIGHT) { // a b c z y x p2_lft.reversePolyPtLinks(); 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 = Edge.Side.RIGHT; } outRec1.bottomPt = null; if (holeStateRec.equals(outRec2)) { if (outRec2.firstLeft != outRec1) { outRec1.firstLeft = outRec2.firstLeft; } outRec1.isHole = outRec2.isHole; } outRec2.setPoints(null); outRec2.bottomPt = null; outRec2.firstLeft = outRec1; final int OKIdx = e1.outIdx; final int ObsoleteIdx = e2.outIdx; e1.outIdx = Edge.UNASSIGNED; // nb: safe because we only get here via // AddLocalMaxPoly e2.outIdx = Edge.UNASSIGNED; Edge e = activeEdges; while (e != null) { if (e.outIdx == ObsoleteIdx) { e.outIdx = OKIdx; e.side = side; break; } e = e.nextInAEL; } outRec2.Idx = outRec1.Idx; } // ------------------------------------------------------------------------------ /** * modified to be compatible with double */ private void buildIntersectList(double topY) { if (activeEdges == null) { return; } // prepare for sorting ... Edge e = activeEdges; sortedEdges = e; while (e != null) { e.prevInSEL = e.prevInAEL; e.nextInSEL = e.nextInAEL; e.getCurrent().setX(Edge.topX(e, topY)); e = e.nextInAEL; } // bubblesort ... boolean isModified = true; while (isModified && sortedEdges != null) { isModified = false; e = sortedEdges; while (e.nextInSEL != null) { final Edge eNext = e.nextInSEL; final DoublePoint[] pt = new DoublePoint[1]; if (e.getCurrent().getX() > eNext.getCurrent().getX()) { intersectPoint(e, eNext, pt); final IntersectNode newNode = new IntersectNode(); newNode.edge1 = e; newNode.Edge2 = eNext; newNode.setPt(new DoublePoint(pt[0])); intersectList.add(newNode); swapPositionsInSEL(e, eNext); isModified = true; } else { e = eNext; } } if (e.prevInSEL != null) { e.prevInSEL.nextInSEL = null; } else { break; } } sortedEdges = null; } // ------------------------------------------------------------------------------ private void buildResult(Paths polyg) { polyg.clear(); for (int i = 0; i < polyOuts.size(); i++) { final OutRec outRec = polyOuts.get(i); if (outRec.getPoints() == null) { continue; } OutPt p = outRec.getPoints().prev; final int cnt = p.getPointCount(); if (cnt < 2) { continue; } final Path pg = new Path(cnt); for (int j = 0; j < cnt; j++) { // pg.add( new LongPoint( p.getPt() ) ); pg.add(new DoublePoint(p.getPt())); p = p.prev; } polyg.add(pg); } } private void buildResult2(PolyTree polytree) { polytree.Clear(); // add each output polygon/contour to polytree ... for (int i = 0; i < polyOuts.size(); i++) { final OutRec outRec = polyOuts.get(i); final int cnt = outRec.getPoints().getPointCount(); if (outRec.isOpen && cnt < 2 || !outRec.isOpen && cnt < 3) { continue; } outRec.fixHoleLinkage(); final PolyNode pn = new PolyNode(); polytree.getAllPolys().add(pn); outRec.polyNode = pn; OutPt op = outRec.getPoints().prev; for (int j = 0; j < cnt; j++) { pn.getPolygon().add(op.getPt()); op = op.prev; } } // fixup PolyNode links etc ... for (int i = 0; i < polyOuts.size(); i++) { final OutRec outRec = polyOuts.get(i); if (outRec.polyNode == null) { continue; } else if (outRec.isOpen) { outRec.polyNode.setOpen(true); polytree.addChild(outRec.polyNode); } else if (outRec.firstLeft != null && outRec.firstLeft.polyNode != null) { outRec.firstLeft.polyNode.addChild(outRec.polyNode); } else { polytree.addChild(outRec.polyNode); } } } private void copyAELToSEL() { Edge e = activeEdges; sortedEdges = e; while (e != null) { e.prevInSEL = e.prevInAEL; e.nextInSEL = e.nextInAEL; e = e.nextInAEL; } } private OutRec createOutRec() { final OutRec result = new OutRec(); result.Idx = Edge.UNASSIGNED; result.isHole = false; result.isOpen = false; result.firstLeft = null; result.setPoints(null); result.bottomPt = null; result.polyNode = null; polyOuts.add(result); result.Idx = polyOuts.size() - 1; return result; } private void deleteFromAEL(Edge e) { final Edge AelPrev = e.prevInAEL; final Edge AelNext = e.nextInAEL; if (AelPrev == null && AelNext == null && e != activeEdges) { return; // already deleted } if (AelPrev != null) { AelPrev.nextInAEL = AelNext; } else { activeEdges = AelNext; } if (AelNext != null) { AelNext.prevInAEL = AelPrev; } e.nextInAEL = null; e.prevInAEL = null; } private void deleteFromSEL(Edge e) { final Edge SelPrev = e.prevInSEL; final Edge SelNext = e.nextInSEL; if (SelPrev == null && SelNext == null && !e.equals(sortedEdges)) { return; // already deleted } if (SelPrev != null) { SelPrev.nextInSEL = SelNext; } else { sortedEdges = SelNext; } if (SelNext != null) { SelNext.prevInSEL = SelPrev; } e.nextInSEL = null; e.prevInSEL = null; } /** * modified to be compatible with double */ private static boolean doHorzSegmentsOverlap(double seg1a, double seg1b, double seg2a, double seg2b) { double seg1min = seg1a; double seg2min = seg2a; double seg1max = seg1b; double seg2max = seg2b; if (seg1a > seg1b) { seg1min = seg1b; seg1max = seg1a; } if (seg2a > seg2b) { seg2min = seg2b; seg2max = seg2a; } return seg1min < seg2max && seg2min < seg1max; } /** * modified to be compatible with double */ private void doMaxima(Edge e) { final Edge eMaxPair = e.getMaximaPair(); if (eMaxPair == null) { if (e.outIdx >= 0) { addOutPt(e, e.getTop()); } deleteFromAEL(e); return; } Edge eNext = e.nextInAEL; while (eNext != null && eNext != eMaxPair) { final DoublePoint tmp = new DoublePoint(e.getTop()); intersectEdges(e, eNext, tmp); e.setTop(new DoublePoint(tmp)); swapPositionsInAEL(e, eNext); eNext = e.nextInAEL; } if (e.outIdx == Edge.UNASSIGNED && eMaxPair.outIdx == Edge.UNASSIGNED) { deleteFromAEL(e); deleteFromAEL(eMaxPair); } else if (e.outIdx >= 0 && eMaxPair.outIdx >= 0) { if (e.outIdx >= 0) { addLocalMaxPoly(e, eMaxPair, e.getTop()); } deleteFromAEL(e); deleteFromAEL(eMaxPair); } else if (e.windDelta == 0) { if (e.outIdx >= 0) { addOutPt(e, e.getTop()); e.outIdx = Edge.UNASSIGNED; } deleteFromAEL(e); if (eMaxPair.outIdx >= 0) { addOutPt(eMaxPair, e.getTop()); eMaxPair.outIdx = Edge.UNASSIGNED; } deleteFromAEL(eMaxPair); } else { throw new IllegalStateException("DoMaxima error"); } } // ------------------------------------------------------------------------------ private void doSimplePolygons() { int i = 0; while (i < polyOuts.size()) { final OutRec outrec = polyOuts.get(i++); OutPt op = outrec.getPoints(); if (op == null || outrec.isOpen) { continue; } do // for each Pt in Polygon until duplicate found do ... { OutPt op2 = op.next; while (op2 != outrec.getPoints()) { if (op.getPt().equals(op2.getPt()) && !op2.next.equals(op) && !op2.prev.equals(op)) { // split the polygon into two ... final OutPt op3 = op.prev; final OutPt op4 = op2.prev; op.prev = op4; op4.next = op; op2.prev = op3; op3.next = op2; outrec.setPoints(op); final OutRec outrec2 = createOutRec(); outrec2.setPoints(op2); updateOutPtIdxs(outrec2); if (poly2ContainsPoly1(outrec2.getPoints(), outrec.getPoints())) { // OutRec2 is contained by OutRec1 ... outrec2.isHole = !outrec.isHole; outrec2.firstLeft = outrec; if (usingPolyTree) { fixupFirstLefts2(outrec2, outrec); } } else if (poly2ContainsPoly1(outrec.getPoints(), outrec2.getPoints())) { // OutRec1 is contained by OutRec2 ... outrec2.isHole = outrec.isHole; outrec.isHole = !outrec2.isHole; outrec2.firstLeft = outrec.firstLeft; outrec.firstLeft = outrec2; if (usingPolyTree) { fixupFirstLefts2(outrec, outrec2); } } else { // the 2 polygons are separate ... outrec2.isHole = outrec.isHole; outrec2.firstLeft = outrec.firstLeft; if (usingPolyTree) { fixupFirstLefts1(outrec, outrec2); } } op2 = op; // ie get ready for the next iteration } op2 = op2.next; } op = op.next; } while (op != outrec.getPoints()); } } // ------------------------------------------------------------------------------ private static boolean EdgesAdjacent(IntersectNode inode) { return inode.edge1.nextInSEL == inode.Edge2 || inode.edge1.prevInSEL == inode.Edge2; } // ------------------------------------------------------------------------------ @Override public boolean execute(ClipType clipType, Paths solution) { return execute(clipType, solution, PolyFillType.EVEN_ODD, PolyFillType.EVEN_ODD); } @Override public boolean execute(ClipType clipType, Paths solution, PolyFillType subjFillType, PolyFillType clipFillType) { synchronized (this) { if (hasOpenPaths) { // throw new IllegalStateException( "Error: PolyTree struct is // need for open path clipping." ); return false; } solution.clear(); this.subjFillType = subjFillType; this.clipFillType = clipFillType; this.clipType = clipType; usingPolyTree = false; boolean succeeded; try { succeeded = executeInternal(); // build the return polygons ... if (succeeded) { buildResult(solution); } return succeeded; } finally { polyOuts.clear(); } } } @Override public boolean execute(ClipType clipType, PolyTree polytree) { return execute(clipType, polytree, PolyFillType.EVEN_ODD, PolyFillType.EVEN_ODD); } @Override public boolean execute(ClipType clipType, PolyTree polytree, PolyFillType subjFillType, PolyFillType clipFillType) { synchronized (this) { this.subjFillType = subjFillType; this.clipFillType = clipFillType; this.clipType = clipType; usingPolyTree = true; boolean succeeded; try { succeeded = executeInternal(); // build the return polygons ... if (succeeded) { buildResult2(polytree); } } finally { polyOuts.clear(); } return succeeded; } } // ------------------------------------------------------------------------------ /** * modified to be compatible with double */ private boolean executeInternal() { try { reset(); if (currentLM == null) { return false; } double botY = popScanbeam(); do { insertLocalMinimaIntoAEL(botY); ghostJoins.clear(); processHorizontals(false); if (scanbeam == null) { break; } final double topY = popScanbeam(); if (!processIntersections(topY)) { return false; } processEdgesAtTopOfScanbeam(topY); botY = topY; } while (scanbeam != null || currentLM != null); for (int i = 0; i < polyOuts.size(); i++) { final OutRec outRec = polyOuts.get(i); if (outRec.getPoints() == null || outRec.isOpen) { continue; } } // fix orientations ... for (int i = 0; i < polyOuts.size(); i++) { final OutRec outRec = polyOuts.get(i); if (outRec.getPoints() == null || outRec.isOpen) { continue; } if ((outRec.isHole ^ reverseSolution) == outRec.area() > 0) { outRec.getPoints().reversePolyPtLinks(); } } joinCommonEdges(); for (int i = 0; i < polyOuts.size(); i++) { final OutRec outRec = polyOuts.get(i); if (outRec.getPoints() != null && !outRec.isOpen) { fixupOutPolygon(outRec); } } if (strictlySimple) { doSimplePolygons(); } return true; } // catch { return false; } finally { joins.clear(); ghostJoins.clear(); } } // ------------------------------------------------------------------------------ private void fixupFirstLefts1(OutRec OldOutRec, OutRec NewOutRec) { for (int i = 0; i < polyOuts.size(); i++) { final OutRec outRec = polyOuts.get(i); if (outRec.getPoints() == null || outRec.firstLeft == null) { continue; } final OutRec firstLeft = outRec.firstLeft.parseFirstLeft(); if (firstLeft.equals(OldOutRec)) { if (poly2ContainsPoly1(outRec.getPoints(), NewOutRec.getPoints())) { outRec.firstLeft = NewOutRec; } } } } private void fixupFirstLefts2(OutRec OldOutRec, OutRec NewOutRec) { for (final OutRec outRec : polyOuts) { if (outRec.firstLeft.equals(OldOutRec)) { outRec.firstLeft = NewOutRec; } } } private boolean fixupIntersectionOrder() { // pre-condition: intersections are sorted bottom-most first. // Now it's crucial that intersections are made only between adjacent // edges, // so to ensure this the order of intersections may need adjusting ... Collections.sort(intersectList, new IntersectNode()); copyAELToSEL(); final int cnt = intersectList.size(); for (int i = 0; i < cnt; i++) { if (!EdgesAdjacent(intersectList.get(i))) { int j = i + 1; while (j < cnt && !EdgesAdjacent(intersectList.get(j))) { j++; } if (j == cnt) { return false; } final IntersectNode tmp = intersectList.get(i); intersectList.set(i, intersectList.get(j)); intersectList.set(j, tmp); } swapPositionsInSEL(intersectList.get(i).edge1, intersectList.get(i).Edge2); } return true; } // ---------------------------------------------------------------------- private void fixupOutPolygon(OutRec outRec) { // FixupOutPolygon() - removes duplicate points and simplifies // consecutive // parallel edges by removing the middle vertex. OutPt lastOK = null; outRec.bottomPt = null; OutPt pp = outRec.getPoints(); for (;;) { if (pp.prev == pp || pp.prev == pp.next) { outRec.setPoints(null); return; } // test for duplicate points and collinear edges ... if (pp.getPt().equals(pp.next.getPt()) || pp.getPt().equals(pp.prev.getPt()) || Point.slopesEqual(pp.prev.getPt(), pp.getPt(), pp.next.getPt()) && (!preserveCollinear || !Point .isPt2BetweenPt1AndPt3(pp.prev.getPt(), pp.getPt(), pp.next.getPt()))) { lastOK = null; pp.prev.next = pp.next; pp.next.prev = pp.prev; pp = pp.prev; } else if (pp == lastOK) { break; } else { if (lastOK == null) { lastOK = pp; } pp = pp.next; } } outRec.setPoints(pp); } private OutRec getOutRec(int idx) { OutRec outrec = polyOuts.get(idx); while (outrec != polyOuts.get(outrec.Idx)) { outrec = polyOuts.get(outrec.Idx); } return outrec; } private void insertEdgeIntoAEL(Edge edge, Edge startEdge0) { Edge startEdge = startEdge0; if (activeEdges == null) { edge.prevInAEL = null; edge.nextInAEL = null; activeEdges = edge; } else if (startEdge == null && Edge.doesE2InsertBeforeE1(activeEdges, edge)) { edge.prevInAEL = null; edge.nextInAEL = activeEdges; activeEdges.prevInAEL = edge; activeEdges = edge; } else { if (startEdge == null) { startEdge = activeEdges; } while (startEdge.nextInAEL != null && !Edge.doesE2InsertBeforeE1(startEdge.nextInAEL, edge)) { startEdge = startEdge.nextInAEL; } edge.nextInAEL = startEdge.nextInAEL; if (startEdge.nextInAEL != null) { startEdge.nextInAEL.prevInAEL = edge; } edge.prevInAEL = startEdge; startEdge.nextInAEL = edge; } } // ------------------------------------------------------------------------------ /** * modified to be compatible with double */ private void insertLocalMinimaIntoAEL(double botY) { while (currentLM != null && currentLM.y == botY) { final Edge lb = currentLM.leftBound; final Edge rb = currentLM.rightBound; popLocalMinima(); OutPt Op1 = null; if (lb == null) { insertEdgeIntoAEL(rb, null); updateWindingCount(rb); if (rb.isContributing(clipFillType, subjFillType, clipType)) { Op1 = addOutPt(rb, rb.getBot()); } } else if (rb == null) { insertEdgeIntoAEL(lb, null); updateWindingCount(lb); if (lb.isContributing(clipFillType, subjFillType, clipType)) { Op1 = addOutPt(lb, lb.getBot()); } insertScanbeam(lb.getTop().getY()); } else { insertEdgeIntoAEL(lb, null); insertEdgeIntoAEL(rb, lb); updateWindingCount(lb); rb.windCnt = lb.windCnt; rb.windCnt2 = lb.windCnt2; if (lb.isContributing(clipFillType, subjFillType, clipType)) { Op1 = addLocalMinPoly(lb, rb, lb.getBot()); } insertScanbeam(lb.getTop().getY()); } if (rb != null) { if (rb.isHorizontal()) { addEdgeToSEL(rb); } else { insertScanbeam(rb.getTop().getY()); } } if (lb == null || rb == null) { continue; } // if output polygons share an Edge with a horizontal rb, they'll // need joining later ... if (Op1 != null && rb.isHorizontal() && ghostJoins.size() > 0 && rb.windDelta != 0) { for (int i = 0; i < ghostJoins.size(); i++) { // if the horizontal Rb and a 'ghost' horizontal overlap, // then convert // the 'ghost' join to a real join ready for later ... final Join j = ghostJoins.get(i); if (doHorzSegmentsOverlap(j.outPt1.getPt().getX(), j.getOffPt().getX(), rb.getBot().getX(), rb.getTop().getX())) { addJoin(j.outPt1, Op1, j.getOffPt()); } } } if (lb.outIdx >= 0 && lb.prevInAEL != null && lb.prevInAEL.getCurrent().getX() == lb.getBot().getX() && lb.prevInAEL.outIdx >= 0 && Edge.slopesEqual(lb.prevInAEL, lb) && lb.windDelta != 0 && lb.prevInAEL.windDelta != 0) { final OutPt Op2 = addOutPt(lb.prevInAEL, lb.getBot()); addJoin(Op1, Op2, lb.getTop()); } if (lb.nextInAEL != rb) { if (rb.outIdx >= 0 && rb.prevInAEL.outIdx >= 0 && Edge.slopesEqual(rb.prevInAEL, rb) && rb.windDelta != 0 && rb.prevInAEL.windDelta != 0) { final OutPt Op2 = addOutPt(rb.prevInAEL, rb.getBot()); addJoin(Op1, Op2, rb.getTop()); } Edge e = lb.nextInAEL; if (e != null) { while (e != rb) { // nb: For calculating winding counts etc, // IntersectEdges() assumes // that param1 will be to the right of param2 ABOVE the // intersection ... // nb: For calculating winding counts etc, // IntersectEdges() assumes // that param1 will be to the right of param2 ABOVE the // intersection ... intersectEdges(rb, e, lb.getCurrent()); // order // important // here e = e.nextInAEL; } } } } } // ------------------------------------------------------------------------------ /** * modified to be compatible with double */ private void insertScanbeam(double y) { if (scanbeam == null) { scanbeam = new Scanbeam(); scanbeam.next = null; scanbeam.y = y; } else if (y > scanbeam.y) { final Scanbeam newSb = new Scanbeam(); newSb.y = y; newSb.next = scanbeam; scanbeam = newSb; } else { Scanbeam sb2 = scanbeam; while (sb2.next != null && y <= sb2.next.y) { sb2 = sb2.next; } if (y == sb2.y) { return; // ie ignores duplicates } final Scanbeam newSb = new Scanbeam(); newSb.y = y; newSb.next = sb2.next; sb2.next = newSb; } } // ------------------------------------------------------------------------------ /** * modified to be compatible with double */ private void intersectEdges(Edge e1, Edge e2, DoublePoint pt) { // 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 // ... final boolean e1Contributing = e1.outIdx >= 0; final boolean e2Contributing = e2.outIdx >= 0; // setZ( pt, e1, e2 ); // if either edge is on an OPEN path ... if (e1.windDelta == 0 || e2.windDelta == 0) { // ignore subject-subject open path intersections UNLESS they // are both open paths, AND they are both 'contributing maximas' ... if (e1.windDelta == 0 && e2.windDelta == 0) { return; } else if (e1.polyTyp == e2.polyTyp && e1.windDelta != e2.windDelta && clipType == ClipType.UNION) { if (e1.windDelta == 0) { if (e2Contributing) { addOutPt(e1, pt); if (e1Contributing) { e1.outIdx = Edge.UNASSIGNED; } } } else { if (e1Contributing) { addOutPt(e2, pt); if (e2Contributing) { e2.outIdx = Edge.UNASSIGNED; } } } } else if (e1.polyTyp != e2.polyTyp) { if (e1.windDelta == 0 && Math.abs(e2.windCnt) == 1 && (clipType != ClipType.UNION || e2.windCnt2 == 0)) { addOutPt(e1, pt); if (e1Contributing) { e1.outIdx = Edge.UNASSIGNED; } } else if (e2.windDelta == 0 && Math.abs(e1.windCnt) == 1 && (clipType != ClipType.UNION || e1.windCnt2 == 0)) { addOutPt(e2, pt); if (e2Contributing) { e2.outIdx = Edge.UNASSIGNED; } } } return; } // update winding counts... // assumes that e1 will be to the Right of e2 ABOVE the intersection if (e1.polyTyp == e2.polyTyp) { if (e1.isEvenOddFillType(clipFillType, subjFillType)) { final 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 (!e2.isEvenOddFillType(clipFillType, subjFillType)) { e1.windCnt2 += e2.windDelta; } else { e1.windCnt2 = e1.windCnt2 == 0 ? 1 : 0; } if (!e1.isEvenOddFillType(clipFillType, subjFillType)) { e2.windCnt2 -= e1.windDelta; } else { e2.windCnt2 = e2.windCnt2 == 0 ? 1 : 0; } } PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; if (e1.polyTyp == PolyType.SUBJECT) { e1FillType = subjFillType; e1FillType2 = clipFillType; } else { e1FillType = clipFillType; e1FillType2 = subjFillType; } if (e2.polyTyp == PolyType.SUBJECT) { e2FillType = subjFillType; e2FillType2 = clipFillType; } else { e2FillType = clipFillType; e2FillType2 = subjFillType; } int e1Wc, e2Wc; switch (e1FillType) { case POSITIVE: e1Wc = e1.windCnt; break; case NEGATIVE: e1Wc = -e1.windCnt; break; default: e1Wc = Math.abs(e1.windCnt); break; } switch (e2FillType) { case POSITIVE: e2Wc = e2.windCnt; break; case NEGATIVE: e2Wc = -e2.windCnt; break; default: e2Wc = Math.abs(e2.windCnt); break; } if (e1Contributing && e2Contributing) { if (e1Wc != 0 && e1Wc != 1 || e2Wc != 0 && e2Wc != 1 || e1.polyTyp != e2.polyTyp && clipType != ClipType.XOR) { addLocalMaxPoly(e1, e2, pt); } else { addOutPt(e1, pt); addOutPt(e2, pt); Edge.swapSides(e1, e2); Edge.swapPolyIndexes(e1, e2); } } else if (e1Contributing) { if (e2Wc == 0 || e2Wc == 1) { addOutPt(e1, pt); Edge.swapSides(e1, e2); Edge.swapPolyIndexes(e1, e2); } } else if (e2Contributing) { if (e1Wc == 0 || e1Wc == 1) { addOutPt(e2, pt); Edge.swapSides(e1, e2); Edge.swapPolyIndexes(e1, e2); } } else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) { // neither edge is currently contributing ... int e1Wc2, e2Wc2; switch (e1FillType2) { case POSITIVE: e1Wc2 = e1.windCnt2; break; case NEGATIVE: e1Wc2 = -e1.windCnt2; break; default: e1Wc2 = Math.abs(e1.windCnt2); break; } switch (e2FillType2) { case POSITIVE: e2Wc2 = e2.windCnt2; break; case NEGATIVE: e2Wc2 = -e2.windCnt2; break; default: e2Wc2 = Math.abs(e2.windCnt2); break; } if (e1.polyTyp != e2.polyTyp) { addLocalMinPoly(e1, e2, pt); } else if (e1Wc == 1 && e2Wc == 1) { switch (clipType) { case INTERSECTION: if (e1Wc2 > 0 && e2Wc2 > 0) { addLocalMinPoly(e1, e2, pt); } break; case UNION: if (e1Wc2 <= 0 && e2Wc2 <= 0) { addLocalMinPoly(e1, e2, pt); } break; case DIFFERENCE: if (e1.polyTyp == PolyType.CLIP && e1Wc2 > 0 && e2Wc2 > 0 || e1.polyTyp == PolyType.SUBJECT && e1Wc2 <= 0 && e2Wc2 <= 0) { addLocalMinPoly(e1, e2, pt); } break; case XOR: addLocalMinPoly(e1, e2, pt); break; } } else { Edge.swapSides(e1, e2); } } } /** * modified to be compatible with double */ private static void intersectPoint(Edge edge1, Edge edge2, DoublePoint[] ipV) { final DoublePoint ip = ipV[0] = new DoublePoint(); double b1, b2; // nb: with very large coordinate values, it's possible for // SlopesEqual() to // return false but for the edge.Dx value be equal due to double // precision rounding. if (MyDouble.exactEqual(edge1.deltaX, edge2.deltaX)) { ip.setY(edge1.getCurrent().getY()); ip.setX(Edge.topX(edge1, ip.getY())); return; } if (edge1.getDelta().getX() == 0) { ip.setX(edge1.getBot().getX()); if (edge2.isHorizontal()) { ip.setY(edge2.getBot().getY()); } else { b2 = edge2.getBot().getY() - edge2.getBot().getX() / edge2.deltaX; ip.setY(ip.getX() / edge2.deltaX + b2); } } else if (edge2.getDelta().getX() == 0) { ip.setX(edge2.getBot().getX()); if (edge1.isHorizontal()) { ip.setY(edge1.getBot().getY()); } else { b1 = edge1.getBot().getY() - edge1.getBot().getX() / edge1.deltaX; ip.setY(ip.getX() / edge1.deltaX + b1); } } else { b1 = edge1.getBot().getX() - edge1.getBot().getY() * edge1.deltaX; b2 = edge2.getBot().getX() - edge2.getBot().getY() * edge2.deltaX; final double q = (b2 - b1) / (edge1.deltaX - edge2.deltaX); ip.setY(q); if (Math.abs(edge1.deltaX) < Math.abs(edge2.deltaX)) { ip.setX(edge1.deltaX * q + b1); } else { ip.setX(edge2.deltaX * q + b2); } } if (ip.getY() < edge1.getTop().getY() || ip.getY() < edge2.getTop().getY()) { if (edge1.getTop().getY() > edge2.getTop().getY()) { ip.setY(edge1.getTop().getY()); } else { ip.setY(edge2.getTop().getY()); } if (Math.abs(edge1.deltaX) < Math.abs(edge2.deltaX)) { ip.setX(Edge.topX(edge1, ip.getY())); } else { ip.setX(Edge.topX(edge2, ip.getY())); } } // finally, don't allow 'ip' to be BELOW curr.getY() (ie bottom of // scanbeam) ... if (ip.getY() > edge1.getCurrent().getY()) { ip.setY(edge1.getCurrent().getY()); // better to use the more vertical edge to derive X ... if (Math.abs(edge1.deltaX) > Math.abs(edge2.deltaX)) { ip.setX(Edge.topX(edge2, ip.getY())); } else { ip.setX(Edge.topX(edge1, ip.getY())); } } } private void joinCommonEdges() { for (int i = 0; i < joins.size(); i++) { final Join join = joins.get(i); final OutRec outRec1 = getOutRec(join.outPt1.idx); OutRec outRec2 = getOutRec(join.outPt2.idx); if (outRec1.getPoints() == null || outRec2.getPoints() == null) { continue; } // get the polygon fragment with the correct hole state (FirstLeft) // before calling JoinPoints() ... OutRec holeStateRec; if (outRec1 == outRec2) { holeStateRec = outRec1; } else if (isParam1RightOfParam2(outRec1, outRec2)) { holeStateRec = outRec2; } else if (isParam1RightOfParam2(outRec2, outRec1)) { holeStateRec = outRec1; } else { holeStateRec = OutPt.getLowerMostRec(outRec1, outRec2); } if (!joinPoints(join, outRec1, outRec2)) { continue; } if (outRec1 == outRec2) { // instead of joining two polygons, we've just created a new one // by // splitting one polygon into two. outRec1.setPoints(join.outPt1); outRec1.bottomPt = null; outRec2 = createOutRec(); outRec2.setPoints(join.outPt2); // update all OutRec2.Pts Idx's ... updateOutPtIdxs(outRec2); // We now need to check every OutRec.FirstLeft pointer. If it // points // to OutRec1 it may need to point to OutRec2 instead ... if (usingPolyTree) { for (int j = 0; j < polyOuts.size() - 1; j++) { final OutRec oRec = polyOuts.get(j); if (oRec.getPoints() == null || oRec.firstLeft.parseFirstLeft() != outRec1 || oRec.isHole == outRec1.isHole) { continue; } if (poly2ContainsPoly1(oRec.getPoints(), join.outPt2)) { oRec.firstLeft = outRec2; } } } if (poly2ContainsPoly1(outRec2.getPoints(), outRec1.getPoints())) { // outRec2 is contained by outRec1 ... outRec2.isHole = !outRec1.isHole; outRec2.firstLeft = outRec1; // fixup FirstLeft pointers that may need reassigning to // OutRec1 if (usingPolyTree) { fixupFirstLefts2(outRec2, outRec1); } if ((outRec2.isHole ^ reverseSolution) == outRec2 .area() > 0) { outRec2.getPoints().reversePolyPtLinks(); } } else if (poly2ContainsPoly1(outRec1.getPoints(), outRec2.getPoints())) { // outRec1 is contained by outRec2 ... outRec2.isHole = outRec1.isHole; outRec1.isHole = !outRec2.isHole; outRec2.firstLeft = outRec1.firstLeft; outRec1.firstLeft = outRec2; // fixup FirstLeft pointers that may need reassigning to // OutRec1 if (usingPolyTree) { fixupFirstLefts2(outRec1, outRec2); } if ((outRec1.isHole ^ reverseSolution) == outRec1 .area() > 0) { outRec1.getPoints().reversePolyPtLinks(); } } else { // the 2 polygons are completely separate ... outRec2.isHole = outRec1.isHole; outRec2.firstLeft = outRec1.firstLeft; // fixup FirstLeft pointers that may need reassigning to // OutRec2 if (usingPolyTree) { fixupFirstLefts1(outRec1, outRec2); } } } else { // joined 2 polygons together ... outRec2.setPoints(null); outRec2.bottomPt = null; outRec2.Idx = outRec1.Idx; outRec1.isHole = holeStateRec.isHole; if (holeStateRec == outRec2) { outRec1.firstLeft = outRec2.firstLeft; } outRec2.firstLeft = outRec1; // fixup FirstLeft pointers that may need reassigning to OutRec1 if (usingPolyTree) { fixupFirstLefts2(outRec2, outRec1); } } } } /** * modified to be compatible with double */ private double popScanbeam() { final double y = scanbeam.y; scanbeam = scanbeam.next; return y; } /** * modified to be compatible with double */ private void processEdgesAtTopOfScanbeam(double topY) { Edge e = 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. boolean IsMaximaEdge = e.isMaxima(topY); if (IsMaximaEdge) { final Edge eMaxPair = e.getMaximaPair(); IsMaximaEdge = eMaxPair == null || !eMaxPair.isHorizontal(); } if (IsMaximaEdge) { final Edge ePrev = e.prevInAEL; doMaxima(e); if (ePrev == null) { e = activeEdges; } else { e = ePrev.nextInAEL; } } else { // 2. promote horizontal edges, otherwise update Curr.getX() and // Curr.getY() ... if (e.isIntermediate(topY) && e.nextInLML.isHorizontal()) { final Edge[] t = new Edge[] { e }; updateEdgeIntoAEL(t); e = t[0]; if (e.outIdx >= 0) { addOutPt(e, e.getBot()); } addEdgeToSEL(e); } else { e.getCurrent().setX(Edge.topX(e, topY)); e.getCurrent().setY(topY); } if (strictlySimple) { final Edge ePrev = e.prevInAEL; if (e.outIdx >= 0 && e.windDelta != 0 && ePrev != null && ePrev.outIdx >= 0 && ePrev.getCurrent() .getX() == e.getCurrent().getX() && ePrev.windDelta != 0) { final DoublePoint ip = new DoublePoint(e.getCurrent()); // setZ( ip, ePrev, e ); final OutPt op = addOutPt(ePrev, ip); final OutPt op2 = addOutPt(e, ip); addJoin(op, op2, ip); // StrictlySimple (type-3) join } } e = e.nextInAEL; } } // 3. Process horizontals at the Top of the scanbeam ... processHorizontals(true); // 4. Promote intermediate vertices ... e = activeEdges; while (e != null) { if (e.isIntermediate(topY)) { OutPt op = null; if (e.outIdx >= 0) { op = addOutPt(e, e.getTop()); } final Edge[] t = new Edge[] { e }; updateEdgeIntoAEL(t); e = t[0]; // if output polygons share an edge, they'll need joining later // ... final Edge ePrev = e.prevInAEL; final Edge eNext = e.nextInAEL; if (ePrev != null && ePrev.getCurrent().getX() == e.getBot().getX() && ePrev.getCurrent().getY() == e.getBot().getY() && op != null && ePrev.outIdx >= 0 && ePrev.getCurrent().getY() > ePrev.getTop().getY() && Edge.slopesEqual(e, ePrev) && e.windDelta != 0 && ePrev.windDelta != 0) { final OutPt op2 = addOutPt(ePrev, e.getBot()); addJoin(op, op2, e.getTop()); } else if (eNext != null && eNext.getCurrent().getX() == e.getBot().getX() && eNext.getCurrent().getY() == e.getBot().getY() && op != null && eNext.outIdx >= 0 && eNext.getCurrent().getY() > eNext.getTop().getY() && Edge.slopesEqual(e, eNext) && e.windDelta != 0 && eNext.windDelta != 0) { final OutPt op2 = addOutPt(eNext, e.getBot()); addJoin(op, op2, e.getTop()); } } e = e.nextInAEL; } } private void processHorizontal(Edge horzEdge0, boolean isTopOfScanbeam) { final Direction[] dir = new Direction[1]; final double[] horzLeft = new double[1], horzRight = new double[1]; Edge horzEdge = horzEdge0; getHorzDirection(horzEdge, dir, horzLeft, horzRight); Edge eLastHorz = horzEdge, eMaxPair = null; while (eLastHorz.nextInLML != null && eLastHorz.nextInLML.isHorizontal()) { eLastHorz = eLastHorz.nextInLML; } if (eLastHorz.nextInLML == null) { eMaxPair = eLastHorz.getMaximaPair(); } for (;;) { final boolean IsLastHorz = horzEdge == eLastHorz; Edge e = horzEdge.getNextInAEL(dir[0]); while (e != null) { // Break if we've got to the end of an intermediate horizontal // edge ... // nb: Smaller Dx's are to the right of larger Dx's ABOVE the // horizontal. if (e.getCurrent().getX() == horzEdge.getTop().getX() && horzEdge.nextInLML != null && e.deltaX < horzEdge.nextInLML.deltaX) { break; } final Edge eNext = e.getNextInAEL(dir[0]); // saves eNext for // later if (dir[0] == Direction.LEFT_TO_RIGHT && e.getCurrent().getX() <= horzRight[0] || dir[0] == Direction.RIGHT_TO_LEFT && e.getCurrent().getX() >= horzLeft[0]) { // so far we're still in range of the horizontal Edge but // make sure // we're at the last of consec. horizontals when matching // with eMaxPair if (e == eMaxPair && IsLastHorz) { if (horzEdge.outIdx >= 0) { final OutPt op1 = addOutPt(horzEdge, horzEdge.getTop()); Edge eNextHorz = sortedEdges; while (eNextHorz != null) { if (eNextHorz.outIdx >= 0 && doHorzSegmentsOverlap( horzEdge.getBot().getX(), horzEdge.getTop().getX(), eNextHorz.getBot().getX(), eNextHorz.getTop().getX())) { final OutPt op2 = addOutPt(eNextHorz, eNextHorz.getBot()); addJoin(op2, op1, eNextHorz.getTop()); } eNextHorz = eNextHorz.nextInSEL; } addGhostJoin(op1, horzEdge.getBot()); addLocalMaxPoly(horzEdge, eMaxPair, horzEdge.getTop()); } deleteFromAEL(horzEdge); deleteFromAEL(eMaxPair); return; } else if (dir[0] == Direction.LEFT_TO_RIGHT) { final DoublePoint Pt = new DoublePoint( e.getCurrent().getX(), horzEdge.getCurrent().getY()); intersectEdges(horzEdge, e, Pt); } else { final DoublePoint Pt = new DoublePoint( e.getCurrent().getX(), horzEdge.getCurrent().getY()); intersectEdges(e, horzEdge, Pt); } swapPositionsInAEL(horzEdge, e); } else if (dir[0] == Direction.LEFT_TO_RIGHT && e.getCurrent().getX() >= horzRight[0] || dir[0] == Direction.RIGHT_TO_LEFT && e.getCurrent().getX() <= horzLeft[0]) { break; } e = eNext; } // end while if (horzEdge.nextInLML != null && horzEdge.nextInLML.isHorizontal()) { final Edge[] t = new Edge[] { horzEdge }; updateEdgeIntoAEL(t); horzEdge = t[0]; if (horzEdge.outIdx >= 0) { addOutPt(horzEdge, horzEdge.getBot()); } getHorzDirection(horzEdge, dir, horzLeft, horzRight); } else { break; } } // end for (;;) if (horzEdge.nextInLML != null) { if (horzEdge.outIdx >= 0) { final OutPt op1 = addOutPt(horzEdge, horzEdge.getTop()); if (isTopOfScanbeam) { addGhostJoin(op1, horzEdge.getBot()); } final Edge[] t = new Edge[] { horzEdge }; updateEdgeIntoAEL(t); horzEdge = t[0]; if (horzEdge.windDelta == 0) { return; } // nb: HorzEdge is no longer horizontal here final Edge ePrev = horzEdge.prevInAEL; final Edge eNext = horzEdge.nextInAEL; if (ePrev != null && ePrev.getCurrent().getX() == horzEdge.getBot().getX() && ePrev.getCurrent().getY() == horzEdge.getBot().getY() && ePrev.windDelta != 0 && ePrev.outIdx >= 0 && ePrev.getCurrent().getY() > ePrev.getTop().getY() && Edge.slopesEqual(horzEdge, ePrev)) { final OutPt op2 = addOutPt(ePrev, horzEdge.getBot()); addJoin(op1, op2, horzEdge.getTop()); } else if (eNext != null && eNext.getCurrent().getX() == horzEdge.getBot().getX() && eNext.getCurrent().getY() == horzEdge.getBot().getY() && eNext.windDelta != 0 && eNext.outIdx >= 0 && eNext.getCurrent().getY() > eNext.getTop().getY() && Edge.slopesEqual(horzEdge, eNext)) { final OutPt op2 = addOutPt(eNext, horzEdge.getBot()); addJoin(op1, op2, horzEdge.getTop()); } } else { final Edge[] t = new Edge[] { horzEdge }; updateEdgeIntoAEL(t); // horzEdge = t[0]; } } else { if (horzEdge.outIdx >= 0) { addOutPt(horzEdge, horzEdge.getTop()); } deleteFromAEL(horzEdge); } } // ------------------------------------------------------------------------------ private void processHorizontals(boolean isTopOfScanbeam) { Edge horzEdge = sortedEdges; while (horzEdge != null) { deleteFromSEL(horzEdge); processHorizontal(horzEdge, isTopOfScanbeam); horzEdge = sortedEdges; } } // ------------------------------------------------------------------------------ /** * modified to be compatible with double */ private boolean processIntersections(double topY) { if (activeEdges == null) { return true; } try { buildIntersectList(topY); if (intersectList.size() == 0) { return true; } if (intersectList.size() == 1 || fixupIntersectionOrder()) { processIntersectList(); } else { return false; } } catch (final Exception e) { sortedEdges = null; intersectList.clear(); throw new IllegalStateException("ProcessIntersections error", e); } sortedEdges = null; return true; } private void processIntersectList() { for (int i = 0; i < intersectList.size(); i++) { final IntersectNode iNode = intersectList.get(i); { intersectEdges(iNode.edge1, iNode.Edge2, iNode.getPt()); swapPositionsInAEL(iNode.edge1, iNode.Edge2); } } intersectList.clear(); } // ------------------------------------------------------------------------------ @Override protected void reset() { super.reset(); scanbeam = null; activeEdges = null; sortedEdges = null; LocalMinima lm = minimaList; while (lm != null) { insertScanbeam(lm.y); lm = lm.next; } } private void setHoleState(Edge e, OutRec outRec) { boolean isHole = false; Edge e2 = e.prevInAEL; while (e2 != null) { if (e2.outIdx >= 0 && e2.windDelta != 0) { isHole = !isHole; if (outRec.firstLeft == null) { outRec.firstLeft = polyOuts.get(e2.outIdx); } } e2 = e2.prevInAEL; } if (isHole) { outRec.isHole = true; } } /** * modified to be compatible with double */ // private void setZ( DoublePoint pt, Edge e1, Edge e2 ) { // if (pt.getZ() != 0 || zFillFunction == null) { // return; // } // else if (pt.equals( e1.getBot() )) { // pt.setZ( e1.getBot().getZ() ); // } // else if (pt.equals( e1.getTop() )) { // pt.setZ( e1.getTop().getZ() ); // } // else if (pt.equals( e2.getBot() )) { // pt.setZ( e2.getBot().getZ() ); // } // else if (pt.equals( e2.getTop() )) { // pt.setZ( e2.getTop().getZ() ); // } // else { // zFillFunction.zFill( e1.getBot(), e1.getTop(), e2.getBot(), e2.getTop(), // pt ); // } // } private void swapPositionsInAEL(Edge edge1, Edge edge2) { // check that one or other edge hasn't already been removed from AEL ... if (edge1.nextInAEL == edge1.prevInAEL || edge2.nextInAEL == edge2.prevInAEL) { return; } if (edge1.nextInAEL == edge2) { final Edge next = edge2.nextInAEL; if (next != null) { next.prevInAEL = edge1; } final Edge 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) { final Edge next = edge1.nextInAEL; if (next != null) { next.prevInAEL = edge2; } final Edge prev = edge2.prevInAEL; if (prev != null) { prev.nextInAEL = edge1; } edge1.prevInAEL = prev; edge1.nextInAEL = edge2; edge2.prevInAEL = edge1; edge2.nextInAEL = next; } else { final Edge next = edge1.nextInAEL; final Edge 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) { activeEdges = edge1; } else if (edge2.prevInAEL == null) { activeEdges = edge2; } } // ------------------------------------------------------------------------------; private void swapPositionsInSEL(Edge edge1, Edge edge2) { if (edge1.nextInSEL == null && edge1.prevInSEL == null) { return; } if (edge2.nextInSEL == null && edge2.prevInSEL == null) { return; } if (edge1.nextInSEL == edge2) { final Edge next = edge2.nextInSEL; if (next != null) { next.prevInSEL = edge1; } final Edge 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) { final Edge next = edge1.nextInSEL; if (next != null) { next.prevInSEL = edge2; } final Edge prev = edge2.prevInSEL; if (prev != null) { prev.nextInSEL = edge1; } edge1.prevInSEL = prev; edge1.nextInSEL = edge2; edge2.prevInSEL = edge1; edge2.nextInSEL = next; } else { final Edge next = edge1.nextInSEL; final Edge 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) { sortedEdges = edge1; } else if (edge2.prevInSEL == null) { sortedEdges = edge2; } } /** * modified to be compatible with double */ private void updateEdgeIntoAEL(Edge[] eV) { Edge e = eV[0]; if (e.nextInLML == null) { throw new IllegalStateException("UpdateEdgeIntoAEL: invalid call"); } final Edge AelPrev = e.prevInAEL; final Edge AelNext = e.nextInAEL; e.nextInLML.outIdx = e.outIdx; if (AelPrev != null) { AelPrev.nextInAEL = e.nextInLML; } else { 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; eV[0] = e = e.nextInLML; e.setCurrent(new DoublePoint(e.getBot())); e.prevInAEL = AelPrev; e.nextInAEL = AelNext; if (!e.isHorizontal()) { insertScanbeam(e.getTop().getY()); } } private static void updateOutPtIdxs(OutRec outrec) { OutPt op = outrec.getPoints(); do { op.idx = outrec.Idx; op = op.prev; } while (op != outrec.getPoints()); } private void updateWindingCount(Edge edge) { Edge e = edge.prevInAEL; // find the edge of the same polytype that immediately preceeds 'edge' // in AEL while (e != null && (e.polyTyp != edge.polyTyp || e.windDelta == 0)) { e = e.prevInAEL; } if (e == null) { edge.windCnt = edge.windDelta == 0 ? 1 : edge.windDelta; edge.windCnt2 = 0; e = activeEdges; // ie get ready to calc WindCnt2 } else if (edge.windDelta == 0 && clipType != ClipType.UNION) { edge.windCnt = 1; edge.windCnt2 = e.windCnt2; e = e.nextInAEL; // ie get ready to calc WindCnt2 } else if (edge.isEvenOddFillType(clipFillType, subjFillType)) { // EvenOdd filling ... if (edge.windDelta == 0) { // are we inside a subj polygon ... boolean Inside = true; Edge e2 = e.prevInAEL; while (e2 != null) { if (e2.polyTyp == e.polyTyp && e2.windDelta != 0) { Inside = !Inside; } e2 = e2.prevInAEL; } edge.windCnt = Inside ? 0 : 1; } else { edge.windCnt = edge.windDelta; } edge.windCnt2 = e.windCnt2; e = e.nextInAEL; // ie get ready to calc WindCnt2 } else { // nonZero, Positive or Negative filling ... if (e.windCnt * e.windDelta < 0) { // prev edge is 'decreasing' WindCount (WC) toward zero // so we're outside the previous polygon ... if (Math.abs(e.windCnt) > 1) { // outside prev poly but still inside another. // when reversing direction of prev poly use the same WC if (e.windDelta * edge.windDelta < 0) { edge.windCnt = e.windCnt; } else { edge.windCnt = e.windCnt + edge.windDelta; } } else { // now outside all polys of same polytype so set own WC ... edge.windCnt = edge.windDelta == 0 ? 1 : edge.windDelta; } } else { // prev edge is 'increasing' WindCount (WC) away from zero // so we're inside the previous polygon ... if (edge.windDelta == 0) { edge.windCnt = e.windCnt < 0 ? e.windCnt - 1 : e.windCnt + 1; } else if (e.windDelta * 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 (edge.isEvenOddAltFillType(clipFillType, subjFillType)) { // EvenOdd filling ... while (e != edge) { if (e.windDelta != 0) { edge.windCnt2 = edge.windCnt2 == 0 ? 1 : 0; } e = e.nextInAEL; } } else { // nonZero, Positive or Negative filling ... while (e != edge) { edge.windCnt2 += e.windDelta; e = e.nextInAEL; } } } } // end Clipper