com.itextpdf.text.pdf.parser.clipper.DefaultClipper.java Source code

Java tutorial

Introduction

Here is the source code for com.itextpdf.text.pdf.parser.clipper.DefaultClipper.java

Source

/*
 *
 * This file is part of the iText (R) project.
 * Copyright (c) 2014-2015 iText Group NV
 * Authors: Bruno Lowagie, Paulo Soares, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: sales@itextpdf.com
 *
 *
 * This class is based on the C# open source freeware library Clipper:
 * http://www.angusj.com/delphi/clipper.php
 * The original classes were distributed under the Boost Software License:
 *
 * Freeware for both open source and commercial applications
 * Copyright 2010-2014 Angus Johnson
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
package com.itextpdf.text.pdf.parser.clipper;

import com.itextpdf.text.pdf.parser.clipper.Path.Join;
import com.itextpdf.text.pdf.parser.clipper.Path.OutRec;
import com.itextpdf.text.pdf.parser.clipper.Point.LongPoint;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Logger;

public class DefaultClipper extends ClipperBase {
    private class IntersectNode {
        Edge edge1;
        Edge Edge2;
        private LongPoint pt;

        public LongPoint getPt() {
            return pt;
        }

        public void setPt(LongPoint pt) {
            this.pt = pt;
        }

    };

    private static void getHorzDirection(Edge HorzEdge, Direction[] Dir, long[] Left, long[] 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;
        }
    }

    private static boolean getOverlap(long a1, long a2, long b1, long b2, long[] Left, long[] 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 outRec1, OutRec outRec2) {
        do {
            outRec1 = outRec1.firstLeft;
            if (outRec1 == outRec2) {
                return true;
            }
        } while (outRec1 != null);
        return false;
    }

    //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
    private static int isPointInPolygon(LongPoint pt, Path.OutPt op) {
        //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
        int result = 0;
        final Path.OutPt startOp = op;
        final long ptx = pt.getX(), pty = pt.getY();
        long poly0x = op.getPt().getX(), poly0y = op.getPt().getY();
        do {
            op = op.next;
            final long 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 = (double) (poly0x - ptx) * (poly1y - pty)
                                - (double) (poly1x - ptx) * (poly0y - pty);
                        if (d == 0) {
                            return -1;
                        }
                        if (d > 0 == poly1y > poly0y) {
                            result = 1 - result;
                        }
                    }
                } else {
                    if (poly1x > ptx) {
                        final double d = (double) (poly0x - ptx) * (poly1y - pty)
                                - (double) (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;
    }

    //------------------------------------------------------------------------------
    private static boolean joinHorz(Path.OutPt op1, Path.OutPt op1b, Path.OutPt op2, Path.OutPt op2b, LongPoint Pt,
            boolean DiscardLeft) {
        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(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(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(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(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;
    }

    private boolean joinPoints(Join j, OutRec outRec1, OutRec outRec2) {
        Path.OutPt op1 = j.outPt1, op1b;
        Path.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;
            } else {
                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 long[] LeftV = new long[1], RightV = new long[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 long Left = LeftV[0];
            final long 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 ...
            LongPoint Pt;
            boolean DiscardLeftSide;
            if (op1.getPt().getX() >= Left && op1.getPt().getX() <= Right) {
                Pt = new LongPoint(op1.getPt());
                DiscardLeftSide = op1.getPt().getX() > op1b.getPt().getX();
            } else if (op2.getPt().getX() >= Left && op2.getPt().getX() <= Right) {
                Pt = new LongPoint(op2.getPt());
                DiscardLeftSide = op2.getPt().getX() > op2b.getPt().getX();
            } else if (op1b.getPt().getX() >= Left && op1b.getPt().getX() <= Right) {
                Pt = new LongPoint(op1b.getPt());
                DiscardLeftSide = op1b.getPt().getX() > op1.getPt().getX();
            } else {
                Pt = new LongPoint(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(), useFullRange);
            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(), useFullRange)) {
                    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(), useFullRange);
            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(), useFullRange)) {
                    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;
            } else {
                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;
            }
        }
    }

    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 LongPoint ip : pattern) {
                    p.add(new LongPoint(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 LongPoint ip : pattern) {
                    p.add(new LongPoint(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(Path.OutPt outPt1, Path.OutPt outPt2) {
        Path.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;
    }

    protected final List<OutRec> polyOuts;

    private ClipType clipType;

    private Scanbeam scanbeam;

    private Path.Maxima maxima;

    private Edge activeEdges;

    private Edge sortedEdges;

    private final List<IntersectNode> intersectList;

    private final Comparator<IntersectNode> intersectNodeComparer;

    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;

    private final static Logger LOGGER = Logger.getLogger(DefaultClipper.class.getName());

    public DefaultClipper() {
        this(0);
    }

    public DefaultClipper(int InitOptions) //constructor
    {
        super((PRESERVE_COLINEAR & InitOptions) != 0);
        scanbeam = null;
        maxima = null;
        activeEdges = null;
        sortedEdges = null;
        intersectList = new ArrayList<IntersectNode>();
        intersectNodeComparer = new Comparator<IntersectNode>() {
            public int compare(IntersectNode o1, IntersectNode o2) {
                final long i = o2.getPt().getY() - o1.getPt().getY();
                if (i > 0) {
                    return 1;
                } else if (i < 0) {
                    return -1;
                } else {
                    return 0;
                }
            }
        };

        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 insertScanbeam(long Y) {
        //single-linked list: sorted descending, ignoring dups.
        if (scanbeam == null) {
            scanbeam = new Scanbeam();
            scanbeam.next = null;
            scanbeam.y = Y;
        } else if (Y > scanbeam.y) {
            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
            Scanbeam newSb = new Scanbeam();
            newSb.y = Y;
            newSb.next = sb2.next;
            sb2.next = newSb;
        }
    }
    //------------------------------------------------------------------------------

    private void InsertMaxima(long X) {
        //double-linked list: sorted ascending, ignoring dups.
        Path.Maxima newMax = new Path.Maxima();
        newMax.X = X;
        if (maxima == null) {
            maxima = newMax;
            maxima.Next = null;
            maxima.Prev = null;
        } else if (X < maxima.X) {
            newMax.Next = maxima;
            newMax.Prev = null;
            maxima = newMax;
        } else {
            Path.Maxima m = maxima;
            while (m.Next != null && (X >= m.Next.X))
                m = m.Next;
            if (X == m.X)
                return; //ie ignores duplicates (& CG to clean up newMax)
            //insert newMax between m and m.Next ...
            newMax.Next = m.Next;
            newMax.Prev = m;
            if (m.Next != null)
                m.Next.Prev = newMax;
            m.Next = newMax;
        }
    }
    //------------------------------------------------------------------------------

    private void addEdgeToSEL(Edge edge) {
        LOGGER.entering(DefaultClipper.class.getName(), "addEdgeToSEL");

        //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;
        }
    }

    private void addGhostJoin(Path.OutPt Op, LongPoint OffPt) {
        final Join j = new Join();
        j.outPt1 = Op;
        j.setOffPt(OffPt);
        ghostJoins.add(j);
    }

    //------------------------------------------------------------------------------

    private void addJoin(Path.OutPt Op1, Path.OutPt Op2, LongPoint OffPt) {
        LOGGER.entering(DefaultClipper.class.getName(), "addJoin");
        final Join j = new Join();
        j.outPt1 = Op1;
        j.outPt2 = Op2;
        j.setOffPt(OffPt);
        joins.add(j);
    }

    //------------------------------------------------------------------------------

    private void addLocalMaxPoly(Edge e1, Edge e2, LongPoint 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);
        }
    }

    //------------------------------------------------------------------------------

    private Path.OutPt addLocalMinPoly(Edge e1, Edge e2, LongPoint pt) {
        LOGGER.entering(DefaultClipper.class.getName(), "addLocalMinPoly");
        Path.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, useFullRange) && e.windDelta != 0 && prevE.windDelta != 0) {
            final Path.OutPt outPt = addOutPt(prevE, pt);
            addJoin(result, outPt, e.getTop());
        }
        return result;
    }

    private Path.OutPt addOutPt(Edge e, LongPoint pt) {
        LOGGER.entering(DefaultClipper.class.getName(), "addOutPt");
        if (e.outIdx < 0) {
            OutRec outRec = createOutRec();
            outRec.isOpen = (e.windDelta == 0);
            Path.OutPt newOp = new Path.OutPt();
            outRec.pts = newOp;
            newOp.idx = outRec.Idx;
            newOp.pt = pt;
            newOp.next = newOp;
            newOp.prev = newOp;
            if (!outRec.isOpen)
                setHoleState(e, outRec);
            e.outIdx = outRec.Idx; //nb: do this after SetZ !
            return newOp;
        } else {
            final OutRec outRec = polyOuts.get(e.outIdx);
            //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
            final Path.OutPt op = outRec.getPoints();
            boolean ToFront = (e.side == Edge.Side.LEFT);
            LOGGER.finest("op=" + op.getPointCount());
            LOGGER.finest(ToFront + " " + pt + " " + op.getPt());
            if (ToFront && pt.equals(op.getPt())) {
                return op;
            } else if (!ToFront && pt.equals(op.prev.getPt())) {
                return op.prev;
            }

            final Path.OutPt newOp = new Path.OutPt();
            newOp.idx = outRec.Idx;
            newOp.setPt(new LongPoint(pt));
            newOp.next = op;
            newOp.prev = op.prev;
            newOp.prev.next = newOp;
            op.prev = newOp;
            if (ToFront) {
                outRec.setPoints(newOp);
            }
            return newOp;
        }
    }

    private Path.OutPt GetLastOutPt(Edge e) {
        OutRec outRec = polyOuts.get(e.outIdx);
        if (e.side == Edge.Side.LEFT)
            return outRec.pts;
        else
            return outRec.pts.prev;
    }
    //------------------------------------------------------------------------------

    private void appendPolygon(Edge e1, Edge e2) {
        LOGGER.entering(DefaultClipper.class.getName(), "appendPolygon");

        //get the start and ends of both output polygons ...
        final OutRec outRec1 = polyOuts.get(e1.outIdx);
        final OutRec outRec2 = polyOuts.get(e2.outIdx);
        LOGGER.finest("" + e1.outIdx);
        LOGGER.finest("" + e2.outIdx);

        OutRec holeStateRec;
        if (isParam1RightOfParam2(outRec1, outRec2)) {
            holeStateRec = outRec2;
        } else if (isParam1RightOfParam2(outRec2, outRec1)) {
            holeStateRec = outRec1;
        } else {
            holeStateRec = Path.OutPt.getLowerMostRec(outRec1, outRec2);
        }

        final Path.OutPt p1_lft = outRec1.getPoints();
        final Path.OutPt p1_rt = p1_lft.prev;
        final Path.OutPt p2_lft = outRec2.getPoints();
        final Path.OutPt p2_rt = p2_lft.prev;

        LOGGER.finest("p1_lft.getPointCount() = " + p1_lft.getPointCount());
        LOGGER.finest("p1_rt.getPointCount() = " + p1_rt.getPointCount());
        LOGGER.finest("p2_lft.getPointCount() = " + p2_lft.getPointCount());
        LOGGER.finest("p2_rt.getPointCount() = " + p2_rt.getPointCount());

        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;
    }

    //------------------------------------------------------------------------------

    private void buildIntersectList(long 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 LongPoint[] pt = new LongPoint[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(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;
            }
            Path.OutPt p = outRec.getPoints().prev;
            final int cnt = p.getPointCount();
            LOGGER.finest("cnt = " + cnt);
            if (cnt < 2) {
                continue;
            }
            final Path pg = new Path(cnt);
            for (int j = 0; j < cnt; j++) {
                pg.add(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() != null ? outRec.getPoints().getPointCount() : 0;
            if (outRec.isOpen && cnt < 2 || !outRec.isOpen && cnt < 3) {
                continue;
            }
            outRec.fixHoleLinkage();
            final PolyNode pn = new PolyNode();
            polytree.getAllPolys().add(pn);
            outRec.polyNode = pn;
            Path.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) {
        LOGGER.entering(DefaultClipper.class.getName(), "deleteFromAEL");

        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;
        LOGGER.exiting(DefaultClipper.class.getName(), "deleteFromAEL");
    }

    private void deleteFromSEL(Edge e) {
        LOGGER.entering(DefaultClipper.class.getName(), "deleteFromSEL");

        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;
    }

    private boolean doHorzSegmentsOverlap(long seg1a, long seg1b, long seg2a, long seg2b) {
        if (seg1a > seg1b) {
            final long tmp = seg1a;
            seg1a = seg1b;
            seg1b = tmp;
        }
        if (seg2a > seg2b) {
            final long tmp = seg2a;
            seg2a = seg2b;
            seg2b = tmp;
        }
        return (seg1a < seg2b) && (seg2a < seg1b);
    }

    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 LongPoint tmp = new LongPoint(e.getTop());
            intersectEdges(e, eNext, tmp);
            e.setTop(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++);
            Path.OutPt op = outrec.getPoints();
            if (op == null || outrec.isOpen) {
                continue;
            }
            do //for each Pt in Polygon until duplicate found do ...
            {
                Path.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 Path.OutPt op3 = op.prev;
                        final Path.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 boolean EdgesAdjacent(IntersectNode inode) {
        return inode.edge1.nextInSEL == inode.Edge2 || inode.edge1.prevInSEL == inode.Edge2;
    }

    //------------------------------------------------------------------------------

    public boolean execute(ClipType clipType, Paths solution, PolyFillType FillType) {
        return execute(clipType, solution, FillType, FillType);
    }

    public boolean execute(ClipType clipType, PolyTree polytree) {
        return execute(clipType, polytree, PolyFillType.EVEN_ODD);
    }

    public boolean execute(ClipType clipType, PolyTree polytree, PolyFillType FillType) {
        return execute(clipType, polytree, FillType, FillType);
    }

    public boolean execute(ClipType clipType, Paths solution) {
        return execute(clipType, solution, PolyFillType.EVEN_ODD);
    }

    public boolean execute(ClipType clipType, Paths solution, PolyFillType subjFillType,
            PolyFillType clipFillType) {

        synchronized (this) {

            if (hasOpenPaths) {
                throw new IllegalStateException("Error: PolyTree struct is needed for open path clipping.");
            }

            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();

            }
        }

    }

    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;
        }
    }

    //------------------------------------------------------------------------------

    private boolean executeInternal() {
        try {
            reset();
            if (currentLM == null)
                return false;

            long botY = popScanbeam();
            do {
                insertLocalMinimaIntoAEL(botY);
                processHorizontals();
                ghostJoins.clear();
                if (scanbeam == null)
                    break;
                long topY = popScanbeam();
                if (!processIntersections(topY))
                    return false;
                processEdgesAtTopOfScanbeam(topY);
                botY = topY;
            } while (scanbeam != null || currentLM != null);

            //fix orientations ...
            for (int i = 0; i < polyOuts.size(); i++) {
                OutRec outRec = polyOuts.get(i);
                if (outRec.pts == null || outRec.isOpen)
                    continue;
                if ((outRec.isHole ^ reverseSolution) == (outRec.area() > 0))
                    outRec.getPoints().reversePolyPtLinks();
            }

            joinCommonEdges();

            for (int i = 0; i < polyOuts.size(); i++) {
                OutRec outRec = polyOuts.get(i);
                if (outRec.getPoints() == null)
                    continue;
                else if (outRec.isOpen)
                    fixupOutPolyline(outRec);
                else
                    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 = parseFirstLeft(outRec.firstLeft);
            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 == 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, intersectNodeComparer);

        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 fixupOutPolyline(OutRec outrec) {
        Path.OutPt pp = outrec.pts;
        Path.OutPt lastPP = pp.prev;
        while (pp != lastPP) {
            pp = pp.next;
            if (pp.pt.equals(pp.prev.pt)) {
                if (pp == lastPP)
                    lastPP = pp.prev;
                Path.OutPt tmpPP = pp.prev;
                tmpPP.next = pp.next;
                pp.next.prev = tmpPP;
                pp = tmpPP;
            }
        }
        if (pp == pp.prev)
            outrec.pts = null;
    }

    private void fixupOutPolygon(OutRec outRec) {
        //FixupOutPolygon() - removes duplicate points and simplifies consecutive
        //parallel edges by removing the middle vertex.
        Path.OutPt lastOK = null;
        outRec.bottomPt = null;
        Path.OutPt pp = outRec.getPoints();
        boolean preserveCol = preserveCollinear || strictlySimple;
        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(), useFullRange)
                    && (!preserveCol
                            || !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 startEdge) {
        LOGGER.entering(DefaultClipper.class.getName(), "insertEdgeIntoAEL");

        if (activeEdges == null) {
            edge.prevInAEL = null;
            edge.nextInAEL = null;
            LOGGER.finest("Edge " + edge.outIdx + " -> " + null);
            activeEdges = edge;
        } else if (startEdge == null && Edge.doesE2InsertBeforeE1(activeEdges, edge)) {
            edge.prevInAEL = null;
            edge.nextInAEL = activeEdges;
            LOGGER.finest("Edge " + edge.outIdx + " -> " + edge.nextInAEL.outIdx);
            activeEdges.prevInAEL = edge;
            activeEdges = edge;
        } else {
            LOGGER.finest("activeEdges unchanged");
            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;
        }
    }

    //------------------------------------------------------------------------------

    private void insertLocalMinimaIntoAEL(long botY) {
        LOGGER.entering(DefaultClipper.class.getName(), "insertLocalMinimaIntoAEL");

        while (currentLM != null && currentLM.y == botY) {
            final Edge lb = currentLM.leftBound;
            final Edge rb = currentLM.rightBound;
            popLocalMinima();

            Path.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, useFullRange)
                    && lb.windDelta != 0 && lb.prevInAEL.windDelta != 0) {
                final Path.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, useFullRange)
                        && rb.windDelta != 0 && rb.prevInAEL.windDelta != 0) {
                    final Path.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 ...
                        intersectEdges(rb, e, lb.getCurrent()); //order important here
                        e = e.nextInAEL;

                    }
                }
            }
        }
    }

    //------------------------------------------------------------------------------

    //    private void insertScanbeam( long y ) {
    //        LOGGER.entering( DefaultClipper.class.getName(), "insertScanbeam" );
    //
    //        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;
    //        }
    //    }

    //------------------------------------------------------------------------------

    private void intersectEdges(Edge e1, Edge e2, LongPoint pt) {
        LOGGER.entering(DefaultClipper.class.getName(), "insersectEdges");

        //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);
            }
        }
    }

    private void intersectPoint(Edge edge1, Edge edge2, LongPoint[] ipV) {
        final LongPoint ip = ipV[0] = new LongPoint();

        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 (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(Math.round(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(Math.round(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(Math.round(q));
            if (Math.abs(edge1.deltaX) < Math.abs(edge2.deltaX)) {
                ip.setX(Math.round(edge1.deltaX * q + b1));
            } else {
                ip.setX(Math.round(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;
            }
            if (outRec1.isOpen || outRec2.isOpen)
                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 = Path.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 || parseFirstLeft(oRec.firstLeft) != 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);
                }
            }
        }
    }

    private long popScanbeam() {
        LOGGER.entering(DefaultClipper.class.getName(), "popBeam");

        final long y = scanbeam.y;
        scanbeam = scanbeam.next;
        return y;
    }

    private void processEdgesAtTopOfScanbeam(long topY) {
        LOGGER.entering(DefaultClipper.class.getName(), "processEdgesAtTopOfScanbeam");

        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) {
                if (strictlySimple)
                    InsertMaxima(e.getTop().getX());
                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);
                }

                //When StrictlySimple and 'e' is being touched by another edge, then
                //make sure both edges have a vertex here ...
                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 LongPoint ip = new LongPoint(e.getCurrent());

                        setZ(ip, ePrev, e);

                        final Path.OutPt op = addOutPt(ePrev, ip);
                        final Path.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();
        maxima = null;

        //4. Promote intermediate vertices ...
        e = activeEdges;
        while (e != null) {
            if (e.isIntermediate(topY)) {
                Path.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, useFullRange) && e.windDelta != 0 && ePrev.windDelta != 0) {
                    final Path.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, useFullRange) && e.windDelta != 0 && eNext.windDelta != 0) {
                    final Path.OutPt op2 = addOutPt(eNext, e.getBot());
                    addJoin(op, op2, e.getTop());
                }
            }
            e = e.nextInAEL;
        }
        LOGGER.exiting(DefaultClipper.class.getName(), "processEdgesAtTopOfScanbeam");
    }

    private void processHorizontal(Edge horzEdge) {
        LOGGER.entering(DefaultClipper.class.getName(), "isHorizontal");
        final Direction[] dir = new Direction[1];
        final long[] horzLeft = new long[1], horzRight = new long[1];
        boolean IsOpen = horzEdge.outIdx >= 0 && polyOuts.get(horzEdge.outIdx).isOpen;

        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();
        }

        Path.Maxima currMax = maxima;
        if (currMax != null) {
            //get the first maxima in range (X) ...
            if (dir[0] == Direction.LEFT_TO_RIGHT) {
                while (currMax != null && currMax.X <= horzEdge.getBot().getX())
                    currMax = currMax.Next;
                if (currMax != null && currMax.X >= eLastHorz.getBot().getX())
                    currMax = null;
            } else {
                while (currMax.Next != null && currMax.Next.X < horzEdge.getBot().getX())
                    currMax = currMax.Next;
                if (currMax.X <= eLastHorz.getTop().getX())
                    currMax = null;
            }
        }

        Path.OutPt op1 = null;
        for (;;) { //loop through consec. horizontal edges
            final boolean IsLastHorz = horzEdge == eLastHorz;
            Edge e = horzEdge.getNextInAEL(dir[0]);
            while (e != null) {

                //this code block inserts extra coords into horizontal edges (in output
                //polygons) whereever maxima touch these horizontal edges. This helps
                //'simplifying' polygons (ie if the Simplify property is set).
                if (currMax != null) {
                    if (dir[0] == Direction.LEFT_TO_RIGHT) {
                        while (currMax != null && currMax.X < e.getCurrent().getX()) {
                            if (horzEdge.outIdx >= 0 && !IsOpen)
                                addOutPt(horzEdge, new LongPoint(currMax.X, horzEdge.getBot().getY()));
                            currMax = currMax.Next;
                        }
                    } else {
                        while (currMax != null && currMax.X > e.getCurrent().getX()) {
                            if (horzEdge.outIdx >= 0 && !IsOpen)
                                addOutPt(horzEdge, new LongPoint(currMax.X, horzEdge.getBot().getY()));
                            currMax = currMax.Prev;
                        }
                    }
                }
                ;

                if (dir[0] == Direction.LEFT_TO_RIGHT && e.getCurrent().getX() > horzRight[0]
                        || dir[0] == Direction.RIGHT_TO_LEFT && e.getCurrent().getX() < horzLeft[0])
                    break;
                //Also 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;

                if (horzEdge.outIdx >= 0 && !IsOpen) //note: may be done multiple times
                {
                    op1 = addOutPt(horzEdge, e.getCurrent());
                    Edge eNextHorz = sortedEdges;
                    while (eNextHorz != null) {
                        if (eNextHorz.outIdx >= 0 && doHorzSegmentsOverlap(horzEdge.getBot().getX(),
                                horzEdge.getTop().getX(), eNextHorz.getBot().getX(), eNextHorz.getTop().getX())) {
                            Path.OutPt op2 = GetLastOutPt(eNextHorz);
                            addJoin(op2, op1, eNextHorz.getTop());
                        }
                        eNextHorz = eNextHorz.nextInSEL;
                    }
                    addGhostJoin(op1, horzEdge.getBot());
                }

                //OK, 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)
                        addLocalMaxPoly(horzEdge, eMaxPair, horzEdge.getTop());
                    deleteFromAEL(horzEdge);
                    deleteFromAEL(eMaxPair);
                    return;
                }

                if (dir[0] == Direction.LEFT_TO_RIGHT) {
                    LongPoint Pt = new LongPoint(e.getCurrent().getX(), horzEdge.getCurrent().getY());
                    intersectEdges(horzEdge, e, Pt);
                } else {
                    LongPoint Pt = new LongPoint(e.getCurrent().getX(), horzEdge.getCurrent().getY());
                    intersectEdges(e, horzEdge, Pt);
                }
                Edge eNext = e.getNextInAEL(dir[0]);
                swapPositionsInAEL(horzEdge, e);
                e = eNext;

            } //end while

            //Break out of loop if HorzEdge.NextInLML is not also horizontal ...
            if (horzEdge.nextInLML == null || !horzEdge.nextInLML.isHorizontal())
                break;

            Edge[] temp = new Edge[1];
            temp[0] = horzEdge;
            updateEdgeIntoAEL(temp);
            horzEdge = temp[0];

            if (horzEdge.outIdx >= 0)
                addOutPt(horzEdge, horzEdge.getBot());
            getHorzDirection(horzEdge, dir, horzLeft, horzRight);

        } //end for (;;)

        if (horzEdge.outIdx >= 0 && op1 == null) {
            op1 = GetLastOutPt(horzEdge);
            Edge eNextHorz = sortedEdges;
            while (eNextHorz != null) {
                if (eNextHorz.outIdx >= 0 && doHorzSegmentsOverlap(horzEdge.getBot().getX(),
                        horzEdge.getTop().getX(), eNextHorz.getBot().getX(), eNextHorz.getTop().getX())) {
                    Path.OutPt op2 = GetLastOutPt(eNextHorz);
                    addJoin(op2, op1, eNextHorz.getTop());
                }
                eNextHorz = eNextHorz.nextInSEL;
            }
            addGhostJoin(op1, horzEdge.getTop());
        }

        if (horzEdge.nextInLML != null) {
            if (horzEdge.outIdx >= 0) {
                op1 = addOutPt(horzEdge, horzEdge.getTop());

                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, useFullRange)) {
                    final Path.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, useFullRange)) {
                    final Path.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() {
        LOGGER.entering(DefaultClipper.class.getName(), "processHorizontals");

        Edge horzEdge = sortedEdges;
        while (horzEdge != null) {
            deleteFromSEL(horzEdge);
            processHorizontal(horzEdge);
            horzEdge = sortedEdges;
        }
    }

    //------------------------------------------------------------------------------

    private boolean processIntersections(long topY) {
        LOGGER.entering(DefaultClipper.class.getName(), "processIntersections");

        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;
        maxima = 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;
        }
    }

    private void setZ(LongPoint 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) {
        LOGGER.entering(DefaultClipper.class.getName(), "swapPositionsInAEL");

        //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;
        }

        LOGGER.exiting(DefaultClipper.class.getName(), "swapPositionsInAEL");
    }

    //------------------------------------------------------------------------------;

    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;
        }
    }

    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(e.getBot());
        e.prevInAEL = AelPrev;
        e.nextInAEL = AelNext;
        if (!e.isHorizontal()) {
            insertScanbeam(e.getTop().getY());
        }
    }

    private void updateOutPtIdxs(OutRec outrec) {
        Path.OutPt op = outrec.getPoints();
        do {
            op.idx = outrec.Idx;
            op = op.prev;
        } while (op != outrec.getPoints());
    }

    private void updateWindingCount(Edge edge) {
        LOGGER.entering(DefaultClipper.class.getName(), "updateWindingCount");

        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