diff --git a/clipper-icon.png b/clipper-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..0c86b777f12177528bbad6bee3e61c8484aea08c
Binary files /dev/null and b/clipper-icon.png differ
diff --git a/clipper/.gitignore b/clipper/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..9f0fc218e1a4d3b59fc5570f827b1e9f020a5bd4
--- /dev/null
+++ b/clipper/.gitignore
@@ -0,0 +1,4 @@
+/.settings/
+/target/
+/.classpath
+/.project
diff --git a/clipper/pom.xml b/clipper/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..13f261a912fbb88ce169bd51c50e5815dc681e6e
--- /dev/null
+++ b/clipper/pom.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.bioimageanalysis.icy</groupId>
+		<artifactId>parent-pom-plugin</artifactId>
+		<version>1.0.4</version>
+	</parent>
+	<artifactId>clipper</artifactId>
+	<version>6.4.2</version>
+	<name>A Polygon clipper library for Icy</name>
+	<description>Polygon Clipper is a library to execute various boolean operations (Union, Difference, XOR, etc.) on arbitrary 2D polygons, e.g. calculate the area in which two polygons overlap. It comes with two Demo applications, one for the console and one using a Swing based GUI. This implementation is taken from the de.lighti.clipper implementation v6.4.2 which uses a Boost Software License - Version 1.0.
+	</description>
+	<dependencies/>
+	<repositories>
+		<repository>
+			<id>icy</id>
+			<url>https://icy-nexus.pasteur.fr/repository/Icy/</url>
+		</repository>
+	</repositories>
+</project>
\ No newline at end of file
diff --git a/clipper/src/main/java/de/lighti/clipper/Clipper.java b/clipper/src/main/java/de/lighti/clipper/Clipper.java
new file mode 100644
index 0000000000000000000000000000000000000000..074e02cc4334e768c57eacfc658f290c9c13dc8a
--- /dev/null
+++ b/clipper/src/main/java/de/lighti/clipper/Clipper.java
@@ -0,0 +1,54 @@
+package de.lighti.clipper;
+
+import de.lighti.clipper.Point.LongPoint;
+
+public interface Clipper {
+    enum ClipType {
+        INTERSECTION, UNION, DIFFERENCE, XOR
+    }
+
+    enum Direction {
+        RIGHT_TO_LEFT, LEFT_TO_RIGHT
+    }
+
+    enum EndType {
+        CLOSED_POLYGON, CLOSED_LINE, OPEN_BUTT, OPEN_SQUARE, OPEN_ROUND
+    }
+
+    enum JoinType {
+        SQUARE, ROUND, MITER
+    }
+
+    enum PolyFillType {
+        EVEN_ODD, NON_ZERO, POSITIVE, NEGATIVE
+    }
+
+    enum PolyType {
+        SUBJECT, CLIP
+    }
+
+    interface ZFillCallback {
+        void zFill(LongPoint bot1, LongPoint top1, LongPoint bot2, LongPoint top2, LongPoint pt);
+    }
+
+    //InitOptions that can be passed to the constructor ...
+    int REVERSE_SOLUTION = 1;
+
+    int STRICTLY_SIMPLE = 2;
+
+    int PRESERVE_COLINEAR = 4;
+
+    boolean addPath(Path pg, PolyType polyType, boolean Closed);
+
+    boolean addPaths(Paths ppg, PolyType polyType, boolean closed);
+
+    void clear();
+
+    boolean execute(ClipType clipType, Paths solution);
+
+    boolean execute(ClipType clipType, Paths solution, PolyFillType subjFillType, PolyFillType clipFillType);
+
+    boolean execute(ClipType clipType, PolyTree polytree);
+
+    boolean execute(ClipType clipType, PolyTree polytree, PolyFillType subjFillType, PolyFillType clipFillType);
+}
diff --git a/clipper/src/main/java/de/lighti/clipper/ClipperBase.java b/clipper/src/main/java/de/lighti/clipper/ClipperBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..642fb1349ab46b67ea69d8dfe8017e9b486f6739
--- /dev/null
+++ b/clipper/src/main/java/de/lighti/clipper/ClipperBase.java
@@ -0,0 +1,691 @@
+package de.lighti.clipper;
+
+import de.lighti.clipper.Path.OutRec;
+import de.lighti.clipper.Point.LongPoint;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+public abstract class ClipperBase implements Clipper {
+    protected class LocalMinima {
+        long y;
+        Edge leftBound;
+        Edge rightBound;
+        LocalMinima next;
+    }
+
+    protected class Scanbeam {
+        long y;
+        Scanbeam next;
+    }
+
+    protected class Maxima {
+        long x;
+        Maxima next;
+        Maxima prev;
+    }
+
+    private static void initEdge(Edge e, Edge eNext, Edge ePrev, LongPoint pt ) {
+        e.next = eNext;
+        e.prev = ePrev;
+        e.setCurrent( new LongPoint( pt ) );
+        e.outIdx = Edge.UNASSIGNED;
+    }
+
+    private static void initEdge2(Edge e, PolyType polyType ) {
+        if (e.getCurrent().getY() >= e.next.getCurrent().getY()) {
+            e.setBot( new LongPoint( e.getCurrent() ) );
+            e.setTop( new LongPoint( e.next.getCurrent() ) );
+        }
+        else {
+            e.setTop( new LongPoint( e.getCurrent() ) );
+            e.setBot( new LongPoint( e.next.getCurrent() ) );
+        }
+        e.updateDeltaX();
+        e.polyTyp = polyType;
+    }
+
+    private static void rangeTest( LongPoint Pt ) {
+
+        if (Pt.getX() > LOW_RANGE || Pt.getY() > LOW_RANGE || -Pt.getX() > LOW_RANGE || -Pt.getY() > LOW_RANGE) {
+            if (Pt.getX() > HI_RANGE || Pt.getY() > HI_RANGE || -Pt.getX() > HI_RANGE || -Pt.getY() > HI_RANGE) {
+                throw new IllegalStateException( "Coordinate outside allowed range" );
+            }
+        }
+    }
+
+    private static Edge removeEdge(Edge e ) {
+        //removes e from double_linked_list (but without removing from memory)
+        e.prev.next = e.next;
+        e.next.prev = e.prev;
+        final Edge result = e.next;
+        e.prev = null; //flag as removed (see ClipperBase.Clear)
+        return result;
+    }
+
+    private final static long LOW_RANGE = 0x3FFFFFFF;
+
+    private final static long HI_RANGE = 0x3FFFFFFFFFFFFFFFL;
+
+    protected LocalMinima minimaList;
+
+    protected LocalMinima currentLM;
+
+    protected Scanbeam scanbeam;
+
+    protected final List<OutRec> polyOuts = new ArrayList<>();
+
+    protected Edge activeEdges;
+
+    protected boolean hasOpenPaths;
+
+    protected final boolean preserveCollinear;
+
+    private final static Logger LOGGER = Logger.getLogger( Clipper.class.getName() );
+
+    protected ClipperBase( boolean preserveCollinear ) //constructor (nb: no external instantiation)
+    {
+        this.preserveCollinear = preserveCollinear;
+        minimaList = null;
+        currentLM = null;
+        hasOpenPaths = false;
+    }
+
+    @Override
+    public boolean addPath(Path pg, PolyType polyType, boolean Closed ) {
+
+        if (!Closed && polyType == PolyType.CLIP) {
+            throw new IllegalStateException( "AddPath: Open paths must be subject." );
+        }
+
+        int highI = pg.size() - 1;
+        if (Closed) {
+            while (highI > 0 && pg.get( highI ).equals( pg.get( 0 ) )) {
+                --highI;
+            }
+        }
+        while (highI > 0 && pg.get( highI ).equals( pg.get( highI - 1 ) )) {
+            --highI;
+        }
+        if (Closed && highI < 2 || !Closed && highI < 1) {
+            return false;
+        }
+
+        //create a new edge array ...
+        final List<Edge> edges = new ArrayList<>( highI + 1 );
+        for (int i = 0; i <= highI; i++) {
+            edges.add( new Edge() );
+        }
+
+        boolean IsFlat = true;
+
+        //1. Basic (first) edge initialization ...
+        edges.get( 1 ).setCurrent( new LongPoint( pg.get( 1 ) ) );
+        rangeTest( pg.get( 0 ) );
+        rangeTest( pg.get( highI ) );
+        initEdge( edges.get( 0 ), edges.get( 1 ), edges.get( highI ), pg.get( 0 ) );
+        initEdge( edges.get( highI ), edges.get( 0 ), edges.get( highI - 1 ), pg.get( highI ) );
+        for (int i = highI - 1; i >= 1; --i) {
+            rangeTest( pg.get( i ) );
+            initEdge( edges.get( i ), edges.get( i + 1 ), edges.get( i - 1 ), pg.get( i ) );
+        }
+        Edge eStart = edges.get( 0 );
+
+        //2. Remove duplicate vertices, and (when closed) collinear edges ...
+        Edge e = eStart, eLoopStop = eStart;
+        for (;;) {
+            //nb: allows matching start and end points when not Closed ...
+            if (e.getCurrent().equals( e.next.getCurrent() ) && (Closed || !e.next.equals( eStart ))) {
+                if (e == e.next) {
+                    break;
+                }
+                if (e == eStart) {
+                    eStart = e.next;
+                }
+                e = removeEdge( e );
+                eLoopStop = e;
+                continue;
+            }
+            if (e.prev == e.next) {
+                break; //only two vertices
+            }
+            else if (Closed && Point.slopesEqual( e.prev.getCurrent(), e.getCurrent(), e.next.getCurrent() )
+                            && (!isPreserveCollinear() || !Point.isPt2BetweenPt1AndPt3( e.prev.getCurrent(), e.getCurrent(), e.next.getCurrent() ))) {
+                //Collinear edges are allowed for open paths but in closed paths
+                //the default is to merge adjacent collinear edges into a single edge.
+                //However, if the PreserveCollinear property is enabled, only overlapping
+                //collinear edges (ie spikes) will be removed from closed paths.
+                if (e == eStart) {
+                    eStart = e.next;
+                }
+                e = removeEdge( e );
+                e = e.prev;
+                eLoopStop = e;
+                continue;
+            }
+            e = e.next;
+            if (e == eLoopStop || !Closed && e.next == eStart) {
+                break;
+            }
+        }
+
+        if (!Closed && e == e.next || Closed && e.prev == e.next) {
+            return false;
+        }
+
+        if (!Closed) {
+            hasOpenPaths = true;
+            eStart.prev.outIdx = Edge.SKIP;
+        }
+
+        //3. Do second stage of edge initialization ...
+        e = eStart;
+        do {
+            initEdge2( e, polyType );
+            e = e.next;
+            if (IsFlat && e.getCurrent().getY() != eStart.getCurrent().getY()) {
+                IsFlat = false;
+            }
+        }
+        while (e != eStart);
+
+        //4. Finally, add edge bounds to LocalMinima list ...
+
+        //Totally flat paths must be handled differently when adding them
+        //to LocalMinima list to avoid endless loops etc ...
+        if (IsFlat) {
+            if (Closed) {
+                return false;
+            }
+            e.prev.outIdx = Edge.SKIP;
+            final LocalMinima locMin = new LocalMinima();
+            locMin.next = null;
+            locMin.y = e.getBot().getY();
+            locMin.leftBound = null;
+            locMin.rightBound = e;
+            locMin.rightBound.side = Edge.Side.RIGHT;
+            locMin.rightBound.windDelta = 0;
+            for ( ; ; ) {
+                if (e.getBot().getX() != e.prev.getTop().getX()) {
+                    e.reverseHorizontal();
+                }
+                if (e.next.outIdx == Edge.SKIP) break;
+                e.nextInLML = e.next;
+                e = e.next;
+            }
+            insertLocalMinima( locMin );
+            return true;
+        }
+
+        boolean leftBoundIsForward;
+        Edge EMin = null;
+
+        //workaround to avoid an endless loop in the while loop below when
+        //open paths have matching start and end points ...
+        if (e.prev.getBot().equals( e.prev.getTop() )) {
+            e = e.next;
+        }
+
+        for (;;) {
+            e = e.findNextLocMin();
+            if (e == EMin) {
+                break;
+            }
+            else if (EMin == null) {
+                EMin = e;
+            }
+
+            //E and E.Prev now share a local minima (left aligned if horizontal).
+            //Compare their slopes to find which starts which bound ...
+            final LocalMinima locMin = new LocalMinima();
+            locMin.next = null;
+            locMin.y = e.getBot().getY();
+            if (e.deltaX < e.prev.deltaX) {
+                locMin.leftBound = e.prev;
+                locMin.rightBound = e;
+                leftBoundIsForward = false; //Q.nextInLML = Q.prev
+            }
+            else {
+                locMin.leftBound = e;
+                locMin.rightBound = e.prev;
+                leftBoundIsForward = true; //Q.nextInLML = Q.next
+            }
+            locMin.leftBound.side = Edge.Side.LEFT;
+            locMin.rightBound.side = Edge.Side.RIGHT;
+
+            if (!Closed) {
+                locMin.leftBound.windDelta = 0;
+            }
+            else if (locMin.leftBound.next == locMin.rightBound) {
+                locMin.leftBound.windDelta = -1;
+            }
+            else {
+                locMin.leftBound.windDelta = 1;
+            }
+            locMin.rightBound.windDelta = -locMin.leftBound.windDelta;
+
+            e = processBound( locMin.leftBound, leftBoundIsForward );
+            if (e.outIdx == Edge.SKIP) {
+                e = processBound( e, leftBoundIsForward );
+            }
+
+            Edge E2 = processBound( locMin.rightBound, !leftBoundIsForward );
+            if (E2.outIdx == Edge.SKIP) {
+                E2 = processBound( E2, !leftBoundIsForward );
+            }
+
+            if (locMin.leftBound.outIdx == Edge.SKIP) {
+                locMin.leftBound = null;
+            }
+            else if (locMin.rightBound.outIdx == Edge.SKIP) {
+                locMin.rightBound = null;
+            }
+            insertLocalMinima( locMin );
+            if (!leftBoundIsForward) {
+                e = E2;
+            }
+        }
+        return true;
+
+    }
+
+    @Override
+    public boolean addPaths(Paths paths, PolyType polyType, boolean closed ) {
+        boolean result = false;
+        for (Path path : paths) {
+            if (addPath(path, polyType, closed)) {
+                result = true;
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public void clear() {
+        disposeLocalMinimaList();
+        hasOpenPaths = false;
+    }
+
+    private void disposeLocalMinimaList() {
+        while (minimaList != null) {
+            final LocalMinima tmpLm = minimaList.next;
+            minimaList = null;
+            minimaList = tmpLm;
+        }
+        currentLM = null;
+    }
+
+    private void insertLocalMinima( LocalMinima newLm ) {
+        if (minimaList == null) {
+            minimaList = newLm;
+        }
+        else if (newLm.y >= minimaList.y) {
+            newLm.next = minimaList;
+            minimaList = newLm;
+        }
+        else {
+            LocalMinima tmpLm = minimaList;
+            while (tmpLm.next != null && newLm.y < tmpLm.next.y) {
+                tmpLm = tmpLm.next;
+            }
+            newLm.next = tmpLm.next;
+            tmpLm.next = newLm;
+        }
+    }
+    private boolean isPreserveCollinear() {
+        return preserveCollinear;
+    }
+
+    protected boolean popLocalMinima( long y, LocalMinima[] current ) {
+        LOGGER.entering( ClipperBase.class.getName(), "popLocalMinima" );
+        current[0] = currentLM;
+        if (currentLM != null && currentLM.y == y) {
+            currentLM = currentLM.next;
+            return true;
+        }
+        return false;
+    }
+
+    private Edge processBound(Edge e, boolean LeftBoundIsForward ) {
+        Edge EStart, result = e;
+        Edge Horz;
+
+        if (result.outIdx == Edge.SKIP) {
+            //check if there are edges beyond the skip edge in the bound and if so
+            //create another LocMin and calling ProcessBound once more ...
+            e = result;
+            if (LeftBoundIsForward) {
+                while (e.getTop().getY() == e.next.getBot().getY()) {
+                    e = e.next;
+                }
+                while (e != result && e.deltaX == Edge.HORIZONTAL) {
+                    e = e.prev;
+                }
+            }
+            else {
+                while (e.getTop().getY() == e.prev.getBot().getY()) {
+                    e = e.prev;
+                }
+                while (e != result && e.deltaX == Edge.HORIZONTAL) {
+                    e = e.next;
+                }
+            }
+            if (e == result) {
+                if (LeftBoundIsForward) {
+                    result = e.next;
+                }
+                else {
+                    result = e.prev;
+                }
+            }
+            else {
+                //there are more edges in the bound beyond result starting with E
+                if (LeftBoundIsForward) {
+                    e = result.next;
+                }
+                else {
+                    e = result.prev;
+                }
+                final LocalMinima locMin = new LocalMinima();
+                locMin.next = null;
+                locMin.y = e.getBot().getY();
+                locMin.leftBound = null;
+                locMin.rightBound = e;
+                e.windDelta = 0;
+                result = processBound( e, LeftBoundIsForward );
+                insertLocalMinima( locMin );
+            }
+            return result;
+        }
+
+        if (e.deltaX == Edge.HORIZONTAL) {
+            //We need to be careful with open paths because this may not be a
+            //true local minima (ie E may be following a skip edge).
+            //Also, consecutive horz. edges may start heading left before going right.
+            if (LeftBoundIsForward) {
+                EStart = e.prev;
+            }
+            else {
+                EStart = e.next;
+            }
+            if (EStart.deltaX == Edge.HORIZONTAL) //ie an adjoining horizontal skip edge
+            {
+                if (EStart.getBot().getX() != e.getBot().getX() && EStart.getTop().getX() != e.getBot().getX()) {
+                    e.reverseHorizontal();
+                }
+            }
+            else if (EStart.getBot().getX() != e.getBot().getX()) {
+                e.reverseHorizontal();
+            }
+        }
+
+        EStart = e;
+        if (LeftBoundIsForward) {
+            while (result.getTop().getY() == result.next.getBot().getY() && result.next.outIdx != Edge.SKIP) {
+                result = result.next;
+            }
+            if (result.deltaX == Edge.HORIZONTAL && result.next.outIdx != Edge.SKIP) {
+                //nb: at the top of a bound, horizontals are added to the bound
+                //only when the preceding edge attaches to the horizontal's left vertex
+                //unless a Skip edge is encountered when that becomes the top divide
+                Horz = result;
+                while (Horz.prev.deltaX == Edge.HORIZONTAL) {
+                    Horz = Horz.prev;
+                }
+                if (Horz.prev.getTop().getX() > result.next.getTop().getX()) {
+                    result = Horz.prev;
+                }
+            }
+            while (e != result) {
+                e.nextInLML = e.next;
+                if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.prev.getTop().getX()) {
+                    e.reverseHorizontal();
+                }
+                e = e.next;
+            }
+            if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.prev.getTop().getX()) {
+                e.reverseHorizontal();
+            }
+            result = result.next; //move to the edge just beyond current bound
+        }
+        else {
+            while (result.getTop().getY() == result.prev.getBot().getY() && result.prev.outIdx != Edge.SKIP) {
+                result = result.prev;
+            }
+            if (result.deltaX == Edge.HORIZONTAL && result.prev.outIdx != Edge.SKIP) {
+                Horz = result;
+                while (Horz.next.deltaX == Edge.HORIZONTAL) {
+                    Horz = Horz.next;
+                }
+                if (Horz.next.getTop().getX() == result.prev.getTop().getX() ||
+                    Horz.next.getTop().getX() > result.prev.getTop().getX()) {
+                        result = Horz.next;
+                }
+            }
+
+            while (e != result) {
+                e.nextInLML = e.prev;
+                if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.next.getTop().getX()) {
+                    e.reverseHorizontal();
+                }
+                e = e.prev;
+            }
+            if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.next.getTop().getX()) {
+                e.reverseHorizontal();
+            }
+            result = result.prev; //move to the edge just beyond current bound
+        }
+        return result;
+    }
+
+    protected void reset() {
+        currentLM = minimaList;
+        if (currentLM == null) {
+            return; //ie nothing to process
+        }
+
+        //reset all edges ...
+        scanbeam = null;
+        LocalMinima lm = minimaList;
+        while (lm != null) {
+            insertScanbeam(lm.y);
+            Edge e = lm.leftBound;
+            if (e != null) {
+                e.setCurrent( new LongPoint( e.getBot() ) );
+                e.outIdx = Edge.UNASSIGNED;
+            }
+            e = lm.rightBound;
+            if (e != null) {
+                e.setCurrent( new LongPoint( e.getBot() ) );
+                e.outIdx = Edge.UNASSIGNED;
+            }
+            lm = lm.next;
+        }
+        activeEdges = null;
+    }
+
+    protected void insertScanbeam( long y ) {
+        LOGGER.entering( ClipperBase.class.getName(), "insertScanbeam" );
+
+        //single-linked list: sorted descending, ignoring dups.
+        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;
+        }
+    }
+
+    protected boolean popScanbeam( long[] y ) {
+        if (scanbeam == null) {
+            y[0] = 0;
+            return false;
+        }
+        y[0] = scanbeam.y;
+        scanbeam = scanbeam.next;
+        return true;
+    }
+
+    protected final boolean localMinimaPending() {
+        return currentLM != null;
+    }
+
+    protected OutRec createOutRec() {
+        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;
+    }
+
+    protected void disposeOutRec( int index ) {
+        OutRec outRec = polyOuts.get( index );
+        outRec.setPoints( null );
+        outRec = null;
+        polyOuts.set( index, null );
+    }
+
+    protected void updateEdgeIntoAEL( Edge e ) {
+        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;
+        e = e.nextInLML;
+        e.setCurrent(e.getBot());
+        e.prevInAEL = aelPrev;
+        e.nextInAEL = aelNext;
+        if (e.isHorizontal()) {
+            insertScanbeam(e.getTop().getY());
+        }
+    }
+
+    protected void swapPositionsInAEL(Edge edge1, Edge edge2 ) {
+        LOGGER.entering( ClipperBase.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( ClipperBase.class.getName(), "swapPositionsInAEL" );
+    }
+
+    protected void deleteFromAEL( Edge e ) {
+        LOGGER.entering( ClipperBase.class.getName(), "deleteFromAEL" );
+
+        Edge aelPrev = e.prevInAEL;
+        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( ClipperBase.class.getName(), "deleteFromAEL" );
+    }
+}
\ No newline at end of file
diff --git a/clipper/src/main/java/de/lighti/clipper/ClipperOffset.java b/clipper/src/main/java/de/lighti/clipper/ClipperOffset.java
new file mode 100644
index 0000000000000000000000000000000000000000..d457b47fa2149f1c44ec70f1270e9fc1b44e6ecc
--- /dev/null
+++ b/clipper/src/main/java/de/lighti/clipper/ClipperOffset.java
@@ -0,0 +1,481 @@
+package de.lighti.clipper;
+
+import de.lighti.clipper.Clipper.*;
+import de.lighti.clipper.Point.DoublePoint;
+import de.lighti.clipper.Point.LongPoint;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ClipperOffset {
+    private static boolean nearZero( double val ) {
+        return val > -TOLERANCE && val < TOLERANCE;
+    }
+
+    private Paths destPolys;
+    private Path srcPoly;
+    private Path destPoly;
+
+    private final List<DoublePoint> normals;
+    private double delta, inA, sin, cos;
+
+    private double miterLim, stepsPerRad;
+    private LongPoint lowest;
+
+    private final PolyNode polyNodes;
+    private final double arcTolerance;
+
+    private final double miterLimit;
+    private final static double TWO_PI = Math.PI * 2;
+
+    private final static double DEFAULT_ARC_TOLERANCE = 0.25;
+
+    private final static double TOLERANCE = 1.0E-20;
+
+    public ClipperOffset() {
+        this( 2, DEFAULT_ARC_TOLERANCE );
+    }
+
+    public ClipperOffset( double miterLimit, double arcTolerance ) {
+        this.miterLimit = miterLimit;
+        this.arcTolerance = arcTolerance;
+        lowest = new LongPoint();
+        lowest.setX( -1L );
+        polyNodes = new PolyNode();
+        normals = new ArrayList<>();
+    }
+
+    public void addPath(Path path, JoinType joinType, EndType endType ) {
+        int highI = path.size() - 1;
+        if (highI < 0) {
+            return;
+        }
+        final PolyNode newNode = new PolyNode();
+        newNode.setJoinType( joinType );
+        newNode.setEndType( endType );
+
+        //strip duplicate points from path and also get index to the lowest point ...
+        if (endType == EndType.CLOSED_LINE || endType == EndType.CLOSED_POLYGON) {
+            while (highI > 0 && path.get( 0 ) == path.get( highI )) {
+                highI--;
+            }
+        }
+
+        newNode.getPolygon().add( path.get( 0 ) );
+        int j = 0, k = 0;
+        for (int i = 1; i <= highI; i++) {
+            if (newNode.getPolygon().get( j ) != path.get( i )) {
+                j++;
+                newNode.getPolygon().add( path.get( i ) );
+                if (path.get( i ).getY() > newNode.getPolygon().get( k ).getY() || path.get( i ).getY() == newNode.getPolygon().get( k ).getY()
+                                && path.get( i ).getX() < newNode.getPolygon().get( k ).getX()) {
+                    k = j;
+                }
+            }
+        }
+        if (endType == EndType.CLOSED_POLYGON && j < 2) {
+            return;
+        }
+
+        polyNodes.addChild( newNode );
+
+        //if this path's lowest pt is lower than all the others then update m_lowest
+        if (endType != EndType.CLOSED_POLYGON) {
+            return;
+        }
+        if (lowest.getX() < 0) {
+            lowest = new LongPoint( polyNodes.getChildCount() - 1, k );
+        }
+        else {
+            final LongPoint ip = polyNodes.getChilds().get( (int) lowest.getX() ).getPolygon().get( (int) lowest.getY() );
+            if (newNode.getPolygon().get( k ).getY() > ip.getY() || newNode.getPolygon().get( k ).getY() == ip.getY()
+                            && newNode.getPolygon().get( k ).getX() < ip.getX()) {
+                lowest = new LongPoint( polyNodes.getChildCount() - 1, k );
+            }
+        }
+    }
+
+    public void addPaths(Paths paths, JoinType joinType, EndType endType ) {
+        for (final Path p : paths) {
+            addPath( p, joinType, endType );
+        }
+    }
+
+    public void clear() {
+        polyNodes.getChilds().clear();
+        lowest.setX( -1L );
+    }
+
+    private void doMiter( int j, int k, double r ) {
+        final double q = delta / r;
+        destPoly.add( new LongPoint( Math.round( srcPoly.get( j ).getX() + (normals.get( k ).getX() + normals.get( j ).getX()) * q ), Math
+                        .round( srcPoly.get( j ).getY() + (normals.get( k ).getY() + normals.get( j ).getY()) * q ) ) );
+    }
+
+    private void doOffset( double delta ) {
+        destPolys = new Paths();
+        this.delta = delta;
+
+        //if Zero offset, just copy any CLOSED polygons to m_p and return ...
+        if (nearZero( delta )) {
+            for (int i = 0; i < polyNodes.getChildCount(); i++) {
+                final PolyNode node = polyNodes.getChilds().get( i );
+                if (node.getEndType() == EndType.CLOSED_POLYGON) {
+                    destPolys.add( node.getPolygon() );
+                }
+            }
+            return;
+        }
+
+        //see offset_triginometry3.svg in the documentation folder ...
+        if (miterLimit > 2) {
+            miterLim = 2 / (miterLimit * miterLimit);
+        }
+        else {
+            miterLim = 0.5;
+        }
+
+        double y;
+        if (arcTolerance <= 0.0) {
+            y = DEFAULT_ARC_TOLERANCE;
+        }
+        else if (arcTolerance > Math.abs( delta ) * DEFAULT_ARC_TOLERANCE) {
+            y = Math.abs( delta ) * DEFAULT_ARC_TOLERANCE;
+        }
+        else {
+            y = arcTolerance;
+        }
+        //see offset_triginometry2.svg in the documentation folder ...
+        final double steps = Math.PI / Math.acos( 1 - y / Math.abs( delta ) );
+        sin = Math.sin( TWO_PI / steps );
+        cos = Math.cos( TWO_PI / steps );
+        stepsPerRad = steps / TWO_PI;
+        if (delta < 0.0) {
+            sin = -sin;
+        }
+
+        for (int i = 0; i < polyNodes.getChildCount(); i++) {
+            final PolyNode node = polyNodes.getChilds().get( i );
+            srcPoly = node.getPolygon();
+
+            final int len = srcPoly.size();
+
+            if (len == 0 || delta <= 0 && (len < 3 || node.getEndType() != EndType.CLOSED_POLYGON)) {
+                continue;
+            }
+
+            destPoly = new Path();
+
+            if (len == 1) {
+                if (node.getJoinType() == JoinType.ROUND) {
+                    double X = 1.0, Y = 0.0;
+                    for (int j = 1; j <= steps; j++) {
+                        destPoly.add( new LongPoint( Math.round( srcPoly.get( 0 ).getX() + X * delta ), Math.round( srcPoly.get( 0 ).getY() + Y
+                                        * delta ) ) );
+                        final double X2 = X;
+                        X = X * cos - sin * Y;
+                        Y = X2 * sin + Y * cos;
+                    }
+                }
+                else {
+                    double X = -1.0, Y = -1.0;
+                    for (int j = 0; j < 4; ++j) {
+                        destPoly.add( new LongPoint( Math.round( srcPoly.get( 0 ).getX() + X * delta ), Math.round( srcPoly.get( 0 ).getY() + Y
+                                        * delta ) ) );
+                        if (X < 0) {
+                            X = 1;
+                        }
+                        else if (Y < 0) {
+                            Y = 1;
+                        }
+                        else {
+                            X = -1;
+                        }
+                    }
+                }
+                destPolys.add( destPoly );
+                continue;
+            }
+
+            //build m_normals ...
+            normals.clear();
+            for (int j = 0; j < len - 1; j++) {
+                normals.add( Point.getUnitNormal( srcPoly.get( j ), srcPoly.get( j + 1 ) ) );
+            }
+            if (node.getEndType() == EndType.CLOSED_LINE || node.getEndType() == EndType.CLOSED_POLYGON) {
+                normals.add( Point.getUnitNormal( srcPoly.get( len - 1 ), srcPoly.get( 0 ) ) );
+            }
+            else {
+                normals.add( new DoublePoint( normals.get( len - 2 ) ) );
+            }
+
+            if (node.getEndType() == EndType.CLOSED_POLYGON) {
+                final int[] k = new int[] { len - 1 };
+                for (int j = 0; j < len; j++) {
+                    offsetPoint( j, k, node.getJoinType() );
+                }
+                destPolys.add( destPoly );
+            }
+            else if (node.getEndType() == EndType.CLOSED_LINE) {
+                final int[] k = new int[] { len - 1 };
+                for (int j = 0; j < len; j++) {
+                    offsetPoint( j, k, node.getJoinType() );
+                }
+                destPolys.add( destPoly );
+                destPoly = new Path();
+                //re-build m_normals ...
+                final DoublePoint n = normals.get( len - 1 );
+                for (int j = len - 1; j > 0; j--) {
+                    normals.set( j, new DoublePoint( -normals.get( j - 1 ).getX(), -normals.get( j - 1 ).getY() ) );
+                }
+                normals.set( 0, new DoublePoint( -n.getX(), -n.getY(), 0 ) );
+                k[0] = 0;
+                for (int j = len - 1; j >= 0; j--) {
+                    offsetPoint( j, k, node.getJoinType() );
+                }
+                destPolys.add( destPoly );
+            }
+            else {
+                final int[] k = new int[1];
+                for (int j = 1; j < len - 1; ++j) {
+                    offsetPoint( j, k, node.getJoinType() );
+                }
+
+                LongPoint pt1;
+                if (node.getEndType() == EndType.OPEN_BUTT) {
+                    final int j = len - 1;
+                    pt1 = new LongPoint( Math.round( srcPoly.get( j ).getX() + normals.get( j ).getX() * delta ), Math.round( srcPoly.get( j )
+                                    .getY() + normals.get( j ).getY() * delta ), 0 );
+                    destPoly.add( pt1 );
+                    pt1 = new LongPoint( Math.round( srcPoly.get( j ).getX() - normals.get( j ).getX() * delta ), Math.round( srcPoly.get( j )
+                                    .getY() - normals.get( j ).getY() * delta ), 0 );
+                    destPoly.add( pt1 );
+                }
+                else {
+                    final int j = len - 1;
+                    k[0] = len - 2;
+                    inA = 0;
+                    normals.set( j, new DoublePoint( -normals.get( j ).getX(), -normals.get( j ).getY() ) );
+                    if (node.getEndType() == EndType.OPEN_SQUARE) {
+                        doSquare( j, k[0] );
+                    }
+                    else {
+                        doRound( j, k[0] );
+                    }
+                }
+
+                //re-build m_normals ...
+                for (int j = len - 1; j > 0; j--) {
+                    normals.set( j, new DoublePoint( -normals.get( j - 1 ).getX(), -normals.get( j - 1 ).getY() ) );
+                }
+
+                normals.set( 0, new DoublePoint( -normals.get( 1 ).getX(), -normals.get( 1 ).getY() ) );
+
+                k[0] = len - 1;
+                for (int j = k[0] - 1; j > 0; --j) {
+                    offsetPoint( j, k, node.getJoinType() );
+                }
+
+                if (node.getEndType() == EndType.OPEN_BUTT) {
+                    pt1 = new LongPoint( Math.round( srcPoly.get( 0 ).getX() - normals.get( 0 ).getX() * delta ), Math.round( srcPoly.get( 0 )
+                                    .getY() - normals.get( 0 ).getY() * delta ) );
+                    destPoly.add( pt1 );
+                    pt1 = new LongPoint( Math.round( srcPoly.get( 0 ).getX() + normals.get( 0 ).getX() * delta ), Math.round( srcPoly.get( 0 )
+                                    .getY() + normals.get( 0 ).getY() * delta ) );
+                    destPoly.add( pt1 );
+                }
+                else {
+                    k[0] = 1;
+                    inA = 0;
+                    if (node.getEndType() == EndType.OPEN_SQUARE) {
+                        doSquare( 0, 1 );
+                    }
+                    else {
+                        doRound( 0, 1 );
+                    }
+                }
+                destPolys.add( destPoly );
+            }
+        }
+    }
+
+    private void doRound( int j, int k ) {
+        final double a = Math.atan2( inA, normals.get( k ).getX() * normals.get( j ).getX() + normals.get( k ).getY() * normals.get( j ).getY() );
+        final int steps = Math.max( (int) Math.round( stepsPerRad * Math.abs( a ) ), 1 );
+
+        double X = normals.get( k ).getX(), Y = normals.get( k ).getY(), X2;
+        for (int i = 0; i < steps; ++i) {
+            destPoly.add( new LongPoint( Math.round( srcPoly.get( j ).getX() + X * delta ), Math.round( srcPoly.get( j ).getY() + Y * delta ) ) );
+            X2 = X;
+            X = X * cos - sin * Y;
+            Y = X2 * sin + Y * cos;
+        }
+        destPoly.add( new LongPoint( Math.round( srcPoly.get( j ).getX() + normals.get( j ).getX() * delta ), Math.round( srcPoly.get( j ).getY()
+                        + normals.get( j ).getY() * delta ) ) );
+    }
+
+    private void doSquare( int j, int k ) {
+        final double nkx = normals.get( k ).getX();
+        final double nky = normals.get( k ).getY();
+        final double njx = normals.get( j ).getX();
+        final double njy = normals.get( j ).getY();
+        final double sjx = srcPoly.get( j ).getX();
+        final double sjy = srcPoly.get( j ).getY();
+        final double dx = Math.tan( Math.atan2( inA, nkx * njx + nky * njy ) / 4 );
+        destPoly.add( new LongPoint( Math.round( sjx + delta * (nkx - nky * dx) ), Math.round( sjy + delta * (nky + nkx * dx) ), 0 ) );
+        destPoly.add( new LongPoint( Math.round( sjx + delta * (njx + njy * dx) ), Math.round( sjy + delta * (njy - njx * dx) ), 0 ) );
+    }
+
+    //------------------------------------------------------------------------------
+
+    public void execute(Paths solution, double delta ) {
+        solution.clear();
+        fixOrientations();
+        doOffset( delta );
+        //now clean up 'corners' ...
+        final DefaultClipper clpr = new DefaultClipper( Clipper.REVERSE_SOLUTION );
+        clpr.addPaths( destPolys, PolyType.SUBJECT, true );
+        if (delta > 0) {
+            clpr.execute( ClipType.UNION, solution, PolyFillType.POSITIVE, PolyFillType.POSITIVE );
+        }
+        else {
+            final LongRect r = destPolys.getBounds();
+            final Path outer = new Path( 4 );
+
+            outer.add( new LongPoint( r.left - 10, r.bottom + 10, 0 ) );
+            outer.add( new LongPoint( r.right + 10, r.bottom + 10, 0 ) );
+            outer.add( new LongPoint( r.right + 10, r.top - 10, 0 ) );
+            outer.add( new LongPoint( r.left - 10, r.top - 10, 0 ) );
+
+            clpr.addPath( outer, PolyType.SUBJECT, true );
+
+            clpr.execute( ClipType.UNION, solution, PolyFillType.NEGATIVE, PolyFillType.NEGATIVE );
+            if (solution.size() > 0) {
+                solution.remove( 0 );
+            }
+        }
+    }
+
+    //------------------------------------------------------------------------------
+
+    public void execute(PolyTree solution, double delta ) {
+        solution.Clear();
+        fixOrientations();
+        doOffset( delta );
+
+        //now clean up 'corners' ...
+        final DefaultClipper clpr = new DefaultClipper( Clipper.REVERSE_SOLUTION );
+        clpr.addPaths( destPolys, PolyType.SUBJECT, true );
+        if (delta > 0) {
+            clpr.execute( ClipType.UNION, solution, PolyFillType.POSITIVE, PolyFillType.POSITIVE );
+        }
+        else {
+            final LongRect r = destPolys.getBounds();
+            final Path outer = new Path( 4 );
+
+            outer.add( new LongPoint( r.left - 10, r.bottom + 10, 0 ) );
+            outer.add( new LongPoint( r.right + 10, r.bottom + 10, 0 ) );
+            outer.add( new LongPoint( r.right + 10, r.top - 10, 0 ) );
+            outer.add( new LongPoint( r.left - 10, r.top - 10, 0 ) );
+
+            clpr.addPath( outer, PolyType.SUBJECT, true );
+
+            clpr.execute( ClipType.UNION, solution, PolyFillType.NEGATIVE, PolyFillType.NEGATIVE );
+            //remove the outer PolyNode rectangle ...
+            if (solution.getChildCount() == 1 && solution.getChilds().get( 0 ).getChildCount() > 0) {
+                final PolyNode outerNode = solution.getChilds().get( 0 );
+                solution.getChilds().set( 0, outerNode.getChilds().get( 0 ) );
+                solution.getChilds().get( 0 ).setParent( solution );
+                for (int i = 1; i < outerNode.getChildCount(); i++) {
+                    solution.addChild( outerNode.getChilds().get( i ) );
+                }
+            }
+            else {
+                solution.Clear();
+            }
+        }
+    }
+
+    //------------------------------------------------------------------------------
+
+    private void fixOrientations() {
+        //fixup orientations of all closed paths if the orientation of the
+        //closed path with the lowermost vertex is wrong ...
+        if (lowest.getX() >= 0 && !polyNodes.childs.get( (int) lowest.getX() ).getPolygon().orientation()) {
+            for (int i = 0; i < polyNodes.getChildCount(); i++) {
+                final PolyNode node = polyNodes.childs.get( i );
+                if (node.getEndType() == EndType.CLOSED_POLYGON || node.getEndType() == EndType.CLOSED_LINE && node.getPolygon().orientation()) {
+                    Collections.reverse( node.getPolygon() );
+
+                }
+            }
+        }
+        else {
+            for (int i = 0; i < polyNodes.getChildCount(); i++) {
+                final PolyNode node = polyNodes.childs.get( i );
+                if (node.getEndType() == EndType.CLOSED_LINE && !node.getPolygon().orientation()) {
+                    Collections.reverse( node.getPolygon() );
+                }
+            }
+        }
+    }
+
+    private void offsetPoint( int j, int[] kV, JoinType jointype ) {
+        //cross product ...
+        final int k = kV[0];
+        final double nkx = normals.get( k ).getX();
+        final double nky = normals.get( k ).getY();
+        final double njy = normals.get( j ).getY();
+        final double njx = normals.get( j ).getX();
+        final long sjx = srcPoly.get( j ).getX();
+        final long sjy = srcPoly.get( j ).getY();
+        inA = nkx * njy - njx * nky;
+
+        if (Math.abs( inA * delta ) < 1.0) {
+            //dot product ...
+
+            final double cosA = nkx * njx + njy * nky;
+            if (cosA > 0) // angle ==> 0 degrees
+            {
+                destPoly.add( new LongPoint( Math.round( sjx + nkx * delta ), Math.round( sjy + nky * delta ), 0 ) );
+                return;
+            }
+            //else angle ==> 180 degrees
+        }
+        else if (inA > 1.0) {
+            inA = 1.0;
+        }
+        else if (inA < -1.0) {
+            inA = -1.0;
+        }
+
+        if (inA * delta < 0) {
+            destPoly.add( new LongPoint( Math.round( sjx + nkx * delta ), Math.round( sjy + nky * delta ) ) );
+            destPoly.add( srcPoly.get( j ) );
+            destPoly.add( new LongPoint( Math.round( sjx + njx * delta ), Math.round( sjy + njy * delta ) ) );
+        }
+        else {
+            switch (jointype) {
+                case MITER: {
+                    final double r = 1 + njx * nkx + njy * nky;
+                    if (r >= miterLim) {
+                        doMiter( j, k, r );
+                    }
+                    else {
+                        doSquare( j, k );
+                    }
+                    break;
+                }
+                case SQUARE:
+                    doSquare( j, k );
+                    break;
+                case ROUND:
+                    doRound( j, k );
+                    break;
+            }
+        }
+        kV[0] = j;
+    }
+    //------------------------------------------------------------------------------
+}
\ No newline at end of file
diff --git a/clipper/src/main/java/de/lighti/clipper/DefaultClipper.java b/clipper/src/main/java/de/lighti/clipper/DefaultClipper.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ef5f3eef74eaff37f0e0451b9a2dfd68f33e0be
--- /dev/null
+++ b/clipper/src/main/java/de/lighti/clipper/DefaultClipper.java
@@ -0,0 +1,2519 @@
+package de.lighti.clipper;
+
+import de.lighti.clipper.Path.Join;
+import de.lighti.clipper.Path.OutRec;
+import de.lighti.clipper.Point.LongPoint;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class DefaultClipper extends ClipperBase {
+    private class IntersectNode {
+        Edge edge1;
+        Edge Edge2;
+        private LongPoint pt;
+
+        LongPoint getPt() {
+            return pt;
+        }
+
+        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 isOutRec1RightOfOutRec2(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( new LongPoint( Pt ) );
+                op1b = op1.duplicate( !DiscardLeft );
+            }
+        }
+        else {
+            while (op1.next.getPt().getX() >= Pt.getX() && op1.next.getPt().getX() <= op1.getPt().getX() && op1.next.getPt().getY() == Pt.getY()) {
+                op1 = op1.next;
+            }
+            if (!DiscardLeft && op1.getPt().getX() != Pt.getX()) {
+                op1 = op1.next;
+            }
+            op1b = op1.duplicate( DiscardLeft );
+            if (!op1b.getPt().equals( Pt )) {
+                op1 = op1b;
+                op1.setPt( new LongPoint( Pt ) );
+                op1b = op1.duplicate( DiscardLeft );
+            }
+        }
+
+        if (Dir2 == Direction.LEFT_TO_RIGHT) {
+            while (op2.next.getPt().getX() <= Pt.getX() && op2.next.getPt().getX() >= op2.getPt().getX() && op2.next.getPt().getY() == Pt.getY()) {
+                op2 = op2.next;
+            }
+            if (DiscardLeft && op2.getPt().getX() != Pt.getX()) {
+                op2 = op2.next;
+            }
+            op2b = op2.duplicate( !DiscardLeft );
+            if (!op2b.getPt().equals( Pt )) {
+                op2 = op2b;
+                op2.setPt( new LongPoint( Pt ) );
+                op2b = op2.duplicate( !DiscardLeft );
+            }
+        }
+        else {
+            while (op2.next.getPt().getX() >= Pt.getX() && op2.next.getPt().getX() <= op2.getPt().getX() && op2.next.getPt().getY() == Pt.getY()) {
+                op2 = op2.next;
+            }
+            if (!DiscardLeft && op2.getPt().getX() != Pt.getX()) {
+                op2 = op2.next;
+            }
+            op2b = op2.duplicate( DiscardLeft );
+            if (!op2b.getPt().equals( Pt )) {
+                op2 = op2b;
+                op2.setPt( new LongPoint( 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 static 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 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() );
+            if (Reverse1) {
+                op1b = op1.prev;
+                while (op1b.getPt().equals( op1.getPt() ) && op1b != op1) {
+                    op1b = op1b.prev;
+                }
+                if (op1b.getPt().getY() > op1.getPt().getY() || !Point.slopesEqual( op1.getPt(), op1b.getPt(), j.getOffPt() )) {
+                    return false;
+                }
+            }
+            op2b = op2.next;
+            while (op2b.getPt().equals( op2.getPt() ) && op2b != op2) {
+                op2b = op2b.next;
+            }
+            final boolean Reverse2 = op2b.getPt().getY() > op2.getPt().getY() || !Point.slopesEqual( op2.getPt(), op2b.getPt(), j.getOffPt() );
+            if (Reverse2) {
+                op2b = op2.prev;
+                while (op2b.getPt().equals( op2.getPt() ) && op2b != op2) {
+                    op2b = op2b.prev;
+                }
+                if (op2b.getPt().getY() > op2.getPt().getY() || !Point.slopesEqual( op2.getPt(), op2b.getPt(), j.getOffPt() )) {
+                    return false;
+                }
+            }
+
+            if (op1b == op1 || op2b == op2 || op1b == op2b || outRec1 == outRec2 && Reverse1 == Reverse2) {
+                return false;
+            }
+
+            if (Reverse1) {
+                op1b = op1.duplicate( false );
+                op2b = op2.duplicate( true );
+                op1.prev = op2;
+                op2.next = op1;
+                op1b.next = op2b;
+                op2b.prev = op1b;
+                j.outPt1 = op1;
+                j.outPt2 = op1b;
+                return true;
+            }
+            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;
+    }
+
+    private ClipType clipType;
+
+    private Maxima maxima;
+
+    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;
+
+    private 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<>();
+        intersectNodeComparer = ( node1, node2 ) -> {
+            final long i = node2.getPt().getY() - node1.getPt().getY();
+            if (i > 0) {
+                return 1;
+            }
+            else if (i < 0) {
+                return -1;
+            }
+            else {
+                return 0;
+            }
+        };
+
+        usingPolyTree = false;
+        joins = new ArrayList<>();
+        ghostJoins = new ArrayList<>();
+        reverseSolution = (REVERSE_SOLUTION & InitOptions) != 0;
+        strictlySimple = (STRICTLY_SIMPLE & InitOptions) != 0;
+
+        zFillFunction = null;
+
+    }
+
+    private void addEdgeToSEL( Edge edge ) {
+        LOGGER.entering( DefaultClipper.class.getName(), "addEdgeToSEL" );
+
+        //SEL pointers in PEdge are use to build transient lists of horizontal edges.
+        //However, since we don't need to worry about processing order, all additions
+        //are made to the front of the list ...
+
+        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( new LongPoint( 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( new LongPoint( 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 && prevE.getTop().getY() < pt.getY() && e.getTop().getY() < pt.getY()) {
+            long xPrev = Edge.topX( prevE, pt.getY() );
+            long xE = Edge.topX( e, pt.getY() );
+            if (xPrev == xE && e.windDelta != 0 && prevE.windDelta != 0 &&
+                Point.slopesEqual( new LongPoint( xPrev, pt.getY() ), prevE.getTop(), new LongPoint( xE, pt.getY() ), e.getTop() )) {
+                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) {
+            final OutRec outRec = createOutRec();
+            outRec.isOpen = e.windDelta == 0;
+            final Path.OutPt newOp = new Path.OutPt();
+            outRec.setPoints( newOp );
+            newOp.idx = outRec.Idx;
+            newOp.setPt( new LongPoint( 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();
+            final boolean ToFront = e.side == Edge.Side.LEFT;
+            if (LOGGER.isLoggable( Level.FINEST )) {
+                LOGGER.finest( "op=" + Path.OutPt.getPointCount( op ) );
+                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.getPoints();
+        else
+            return outRec.getPoints().prev;
+    }
+
+    private void appendPolygon(Edge e1, Edge e2 ) {
+        LOGGER.entering( DefaultClipper.class.getName(), "appendPolygon" );
+
+        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 (isOutRec1RightOfOutRec2( outRec1, outRec2 )) {
+            holeStateRec = outRec2;
+        }
+        else if (isOutRec1RightOfOutRec2( outRec2, outRec1 )) {
+            holeStateRec = outRec1;
+        }
+        else {
+            holeStateRec = Path.OutPt.getLowerMostRec( outRec1, outRec2 );
+        }
+
+        //get the start and ends of both output polygons and
+        //join E2 poly onto E1 poly and delete pointers to E2 ...
+        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() = " + Path.OutPt.getPointCount( p1_lft ) );
+        LOGGER.finest( "p1_rt.getPointCount() = " + Path.OutPt.getPointCount( p1_rt ) );
+        LOGGER.finest( "p2_lft.getPointCount() = " + Path.OutPt.getPointCount( p2_lft ) );
+        LOGGER.finest( "p2_rt.getPointCount() = " + Path.OutPt.getPointCount( p2_rt ) );
+
+        //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 );
+            }
+        }
+        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;
+            }
+        }
+        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 = e1.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 );
+                    if (pt[0].getY() < topY) {
+                        pt[0] = new LongPoint( Edge.topX( e, topY ), topY );
+                    }
+                    final IntersectNode newNode = new IntersectNode();
+                    newNode.edge1 = e;
+                    newNode.Edge2 = eNext;
+                    newNode.setPt( new LongPoint( pt[0] ) ); // TODO is new instance necessary?
+                    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 = Path.OutPt.getPointCount( p );
+            LOGGER.finest( "cnt = " + cnt );
+            if (cnt < 2) {
+                continue;
+            }
+            final Path pg = new Path( cnt );
+            for (int j = 0; j < cnt; j++) {
+                pg.add( new LongPoint( 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 = Path.OutPt.getPointCount( outRec.getPoints() );
+            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 boolean deleteFromSEL( Edge[] e ) {
+        LOGGER.entering( DefaultClipper.class.getName(), "deleteFromSEL" );
+
+        //Pop edge from front of SEL (ie SEL is a FILO list)
+        e[0] = sortedEdges;
+        if (e[0] == null) {
+            return false;
+        }
+        final Edge oldE = e[0];
+        sortedEdges = e[0].nextInSEL;
+        if (sortedEdges != null) {
+            sortedEdges.prevInSEL = null;
+        }
+        oldE.nextInSEL = null;
+        oldE.prevInSEL = null;
+        return true;
+    }
+
+    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.getMaximaPairEx();
+        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( new LongPoint( 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;
+    }
+
+    //------------------------------------------------------------------------------
+
+    @Override
+    public boolean execute(ClipType clipType, Paths solution ) {
+        return execute( clipType, solution, PolyFillType.EVEN_ODD, PolyFillType.EVEN_ODD );
+    }
+
+    @Override
+    public boolean execute(ClipType clipType, PolyTree polytree ) {
+        return execute( clipType, polytree, PolyFillType.EVEN_ODD, PolyFillType.EVEN_ODD );
+    }
+
+    @Override
+    public boolean execute(ClipType clipType, Paths solution, PolyFillType subjFillType, PolyFillType clipFillType ) {
+
+        synchronized (this) {
+
+            if (hasOpenPaths) {
+                throw new IllegalStateException( "Error: PolyTree struct is 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();
+
+            }
+        }
+
+    }
+
+    @Override
+    public boolean execute(ClipType clipType, PolyTree polytree, PolyFillType subjFillType, PolyFillType clipFillType ) {
+        synchronized (this) {
+            this.subjFillType = subjFillType;
+            this.clipFillType = clipFillType;
+            this.clipType = clipType;
+            usingPolyTree = true;
+            boolean succeeded;
+            try {
+                succeeded = executeInternal();
+                //build the return polygons ...
+                if (succeeded) {
+                    buildResult2( polytree );
+                }
+            }
+            finally {
+                polyOuts.clear();
+            }
+            return succeeded;
+        }
+    }
+
+    //------------------------------------------------------------------------------
+
+    private boolean executeInternal() {
+        try {
+            reset();
+            sortedEdges = null;
+            maxima = null;
+
+            long[] botY = new long[1], topY = new long[1];
+            if (!popScanbeam( botY )) return false;
+            insertLocalMinimaIntoAEL( botY[0] );
+            while ( popScanbeam( topY ) || localMinimaPending()) {
+                processHorizontals();
+                ghostJoins.clear();
+                if (!processIntersections( topY[0] )) {
+                    return false;
+                }
+                processEdgesAtTopOfScanbeam( topY[0] );
+                botY[0] = topY[0];
+                insertLocalMinimaIntoAEL( botY[0] );
+            }
+
+            //fix orientations ...
+            for (OutRec outRec : polyOuts) {
+                if (outRec.getPoints() == null || outRec.isOpen) {
+                    continue;
+                }
+                if ((outRec.isHole ^ reverseSolution) == outRec.area() > 0) {
+                    outRec.getPoints().reversePolyPtLinks();
+                }
+            }
+
+            joinCommonEdges();
+
+            for (OutRec outRec : polyOuts) {
+                if (outRec.getPoints() == null) {
+                    continue;
+                }
+                else if (outRec.isOpen) {
+                    fixupOutPolygon( 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 (OutRec outRec : polyOuts) {
+            final OutRec firstLeft = Path.OutRec.parseFirstLeft( outRec.firstLeft );
+            if (outRec.getPoints() != null && firstLeft == OldOutRec) {
+                if (poly2ContainsPoly1( outRec.getPoints(), NewOutRec.getPoints() )) {
+                    outRec.firstLeft = NewOutRec;
+                }
+            }
+        }
+    }
+    //------------------------------------------------------------------------------
+
+    private void fixupFirstLefts2(OutRec innerOutRec, OutRec outerOutRec ) {
+        //A polygon has split into two such that one is now the inner of the other.
+        //It's possible that these polygons now wrap around other polygons, so check
+        //every polygon that's also contained by OuterOutRec's FirstLeft container
+        //(including nil) to see if they've become inner to the new inner polygon ...
+        final OutRec orfl = outerOutRec.firstLeft;
+        for (OutRec outRec : polyOuts) {
+            if (outRec.getPoints() == null || outRec == outerOutRec || outRec == innerOutRec) {
+                continue;
+            }
+            final OutRec firstLeft = Path.OutRec.parseFirstLeft( outRec.firstLeft );
+            if (firstLeft != orfl && firstLeft != innerOutRec && firstLeft != outerOutRec) {
+                continue;
+            }
+            if (poly2ContainsPoly1( outRec.getPoints(), innerOutRec.getPoints() )) {
+                outRec.firstLeft = innerOutRec;
+            }
+            else if (poly2ContainsPoly1( outRec.getPoints(), outerOutRec.getPoints() )) {
+                outRec.firstLeft = outerOutRec;
+            }
+            else if (outRec.firstLeft == innerOutRec || outRec.firstLeft == outerOutRec) {
+                outRec.firstLeft = orfl;
+            }
+        }
+    }
+    //----------------------------------------------------------------------
+
+    private void fixupFirstLefts3(OutRec oldOutRec, OutRec newOutRec ) {
+        //same as FixupFirstLefts1 but doesn't call Poly2ContainsPoly1()
+        for (OutRec outRec : polyOuts) {
+            final OutRec firstLeft = Path.OutRec.parseFirstLeft( outRec.firstLeft );
+            if (outRec.getPoints() != null && 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;
+    }
+
+    //----------------------------------------------------------------------
+
+    @SuppressWarnings("unused")
+    private void fixupOutPolyline( OutRec outrec ) {
+        Path.OutPt pp = outrec.getPoints();
+        Path.OutPt lastPP = pp.prev;
+        while (pp != lastPP) {
+            pp = pp.next;
+            if (pp.getPt() == pp.prev.getPt()) {
+                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.setPoints( 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();
+        final 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() )
+                            && (!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" );
+
+        LocalMinima[] lm = new LocalMinima[1];
+        while ( popLocalMinima( botY, lm )) {
+            final Edge lb = lm[0].leftBound;
+            final Edge rb = lm[0].rightBound;
+
+            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()) {
+                    if (rb.nextInLML != null) {
+                    	insertScanbeam( rb.nextInLML.getTop().getY() );
+                    }
+                    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
+                            && Point.slopesEqual( lb.prevInAEL.getCurrent(), lb.prevInAEL.getTop(), lb.getCurrent(), lb.getTop() ) && 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 && Point.slopesEqual( rb.prevInAEL.getCurrent(), rb.prevInAEL.getTop(), rb.getCurrent(), rb.getTop() ) && 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 ...
+                        //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 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 (isOutRec1RightOfOutRec2( outRec1, outRec2 )) {
+                holeStateRec = outRec2;
+            }
+            else if (isOutRec1RightOfOutRec2( 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 );
+
+                if (poly2ContainsPoly1( outRec2.getPoints(), outRec1.getPoints() )) {
+                    //outRec1 contains outRec2 ...
+                    outRec2.isHole = !outRec1.isHole;
+                    outRec2.firstLeft = outRec1;
+
+                    if (usingPolyTree) {
+                        fixupFirstLefts2( outRec2, outRec1 );
+                    }
+
+                    if ((outRec2.isHole ^ reverseSolution) == outRec2.area() > 0) {
+                        outRec2.getPoints().reversePolyPtLinks();
+                    }
+
+                }
+                else if (poly2ContainsPoly1( outRec1.getPoints(), outRec2.getPoints() )) {
+                    //outRec2 contains outRec1 ...
+                    outRec2.isHole = outRec1.isHole;
+                    outRec1.isHole = !outRec2.isHole;
+                    outRec2.firstLeft = outRec1.firstLeft;
+                    outRec1.firstLeft = outRec2;
+
+                    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) {
+                    fixupFirstLefts3( outRec2, outRec1 );
+                }
+            }
+        }
+    }
+
+    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.getMaximaPairEx();
+                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 );
+                    if (e.getTop().getY() == topY) {
+                        e.getCurrent().setZ( e.getTop().getZ() );
+                    }
+                    else if (e.getBot().getY() == topY) {
+                        e.getCurrent().setZ( e.getBot().getZ() );
+                    }
+                    else {
+                        e.getCurrent().setZ( 0L );
+                    }
+                }
+
+                //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() && Point.slopesEqual( e.getCurrent(), e.getTop(), ePrev.getCurrent(), ePrev.getTop() ) && 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() && Point.slopesEqual( e.getCurrent(), e.getTop(), eNext.getCurrent(), eNext.getTop() ) && 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.windDelta == 0;
+
+        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();
+        }
+
+        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.getTop().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;
+        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
+                    if (dir[0] == Direction.LEFT_TO_RIGHT) setZ( e.getCurrent(), horzEdge, e );
+                    else setZ( e.getCurrent(), e, horzEdge );
+
+                    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) {
+                    final LongPoint Pt = new LongPoint( e.getCurrent().getX(), horzEdge.getCurrent().getY() );
+                    intersectEdges( horzEdge, e, Pt );
+                }
+                else {
+                    final LongPoint Pt = new LongPoint( e.getCurrent().getX(), horzEdge.getCurrent().getY() );
+                    intersectEdges( e, horzEdge, Pt );
+                }
+                final 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;
+
+            final Edge[] t = new Edge[] { horzEdge };
+            updateEdgeIntoAEL( t );
+            horzEdge = t[0];
+            if (horzEdge.outIdx >= 0) {
+                addOutPt( horzEdge, horzEdge.getBot() );
+            }
+            getHorzDirection( horzEdge, dir, horzLeft, horzRight );
+
+        } //end for (;;)
+
+        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 )) {
+                    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 )) {
+                    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() {
+        Edge[] horzEdge = new Edge[1]; //m_SortedEdges;
+        while ( deleteFromSEL( horzEdge) ) {
+            processHorizontal( horzEdge[0] );
+        }
+    }
+
+    //------------------------------------------------------------------------------
+
+    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();
+    }
+
+    //------------------------------------------------------------------------------
+
+    private void insertMaxima( long x ) {
+        //double-linked list: sorted ascending, ignoring dups.
+        final Maxima newMax = new 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 {
+            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 setHoleState(Edge e, OutRec outRec ) {
+        Edge e2 = e.prevInAEL;
+        Edge eTmp = null;
+        while (e2 != null) {
+            if (e2.outIdx >= 0 && e2.windDelta != 0) {
+                if (eTmp == null) {
+                    eTmp = e2;
+                }
+                else if (eTmp.outIdx == e2.outIdx) {
+                	eTmp = null; //paired
+                }
+            }
+            e2 = e2.prevInAEL;
+        }
+        if (eTmp == null) {
+            outRec.firstLeft = null;
+            outRec.isHole = false;
+        }
+        else {
+            outRec.firstLeft = polyOuts.get( eTmp.outIdx );
+            outRec.isHole = !outRec.firstLeft.isHole;
+        }
+    }
+
+    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 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( new LongPoint( 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) {
+            PolyFillType pft;
+            pft = (edge.polyTyp == PolyType.SUBJECT ? subjFillType : clipFillType);
+            if (edge.windDelta == 0) {
+                edge.windCnt = (pft == PolyFillType.NEGATIVE ? -1 : 1);
+            }
+            else {
+                edge.windCnt = 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
+
diff --git a/clipper/src/main/java/de/lighti/clipper/Edge.java b/clipper/src/main/java/de/lighti/clipper/Edge.java
new file mode 100644
index 0000000000000000000000000000000000000000..44174b61bc9db74558cec7f74eeddb05985b1a54
--- /dev/null
+++ b/clipper/src/main/java/de/lighti/clipper/Edge.java
@@ -0,0 +1,339 @@
+package de.lighti.clipper;
+
+import de.lighti.clipper.Clipper.ClipType;
+import de.lighti.clipper.Clipper.Direction;
+import de.lighti.clipper.Clipper.PolyFillType;
+import de.lighti.clipper.Clipper.PolyType;
+import de.lighti.clipper.Point.LongPoint;
+
+import java.util.logging.Logger;
+
+class Edge {
+    enum Side {
+        LEFT, RIGHT
+    }
+
+    static boolean doesE2InsertBeforeE1(Edge e1, Edge e2 ) {
+        if (e2.current.getX() == e1.current.getX()) {
+            if (e2.top.getY() > e1.top.getY()) {
+                return e2.top.getX() < topX( e1, e2.top.getY() );
+            }
+            else {
+                return e1.top.getX() > topX( e2, e1.top.getY() );
+            }
+        }
+        else {
+            return e2.current.getX() < e1.current.getX();
+        }
+    }
+
+    static boolean slopesEqual(Edge e1, Edge e2 ) {
+        return e1.getDelta().getY() * e2.getDelta().getX() == e1.getDelta().getX() * e2.getDelta().getY();
+
+    }
+
+    static void swapPolyIndexes(Edge edge1, Edge edge2 ) {
+        final int outIdx = edge1.outIdx;
+        edge1.outIdx = edge2.outIdx;
+        edge2.outIdx = outIdx;
+    }
+
+    static void swapSides(Edge edge1, Edge edge2 ) {
+        final Edge.Side side = edge1.side;
+        edge1.side = edge2.side;
+        edge2.side = side;
+    }
+
+    static long topX(Edge edge, long currentY ) {
+        if (currentY == edge.getTop().getY()) {
+            return edge.getTop().getX();
+        }
+        return edge.getBot().getX() + Math.round( edge.deltaX * (currentY - edge.getBot().getY()) );
+    }
+
+    private final LongPoint bot;
+
+    private final LongPoint current; //current (updated for every new scanbeam)
+
+    private final LongPoint top;
+
+    private final LongPoint delta;
+    double deltaX;
+
+    PolyType polyTyp;
+
+    Edge.Side side; //side only refers to current side of solution poly
+
+    int windDelta; //1 or -1 depending on winding direction
+
+    int windCnt;
+    int windCnt2; //winding count of the opposite polytype
+    int outIdx;
+    Edge next;
+    Edge prev;
+    Edge nextInLML;
+    Edge nextInAEL;
+    Edge prevInAEL;
+    Edge nextInSEL;
+    Edge prevInSEL;
+
+    protected final static int SKIP = -2;
+
+    protected final static int UNASSIGNED = -1;
+
+    protected final static double HORIZONTAL = -3.4E+38;
+
+    private final static Logger LOGGER = Logger.getLogger( Edge.class.getName() );
+
+    public Edge() {
+        delta = new LongPoint();
+        top = new LongPoint();
+        bot = new LongPoint();
+        current = new LongPoint();
+    }
+
+    public Edge findNextLocMin() {
+        Edge e = this;
+        Edge e2;
+        for (;;) {
+            while (!e.bot.equals( e.prev.bot ) || e.current.equals( e.top )) {
+                e = e.next;
+            }
+            if (e.deltaX != Edge.HORIZONTAL && e.prev.deltaX != Edge.HORIZONTAL) {
+                break;
+            }
+            while (e.prev.deltaX == Edge.HORIZONTAL) {
+                e = e.prev;
+            }
+            e2 = e;
+            while (e.deltaX == Edge.HORIZONTAL) {
+                e = e.next;
+            }
+            if (e.top.getY() == e.prev.bot.getY()) {
+                continue; //ie just an intermediate horz.
+            }
+            if (e2.prev.bot.getX() < e.bot.getX()) {
+                e = e2;
+            }
+            break;
+        }
+        return e;
+    }
+
+    public LongPoint getBot() {
+        return bot;
+    }
+
+    public LongPoint getCurrent() {
+        return current;
+    }
+
+    public LongPoint getDelta() {
+        return delta;
+    }
+
+    public Edge getMaximaPair() {
+        if (next.top.equals( top ) && next.nextInLML == null) {
+            return next;
+        }
+        else if (prev.top.equals( top ) && prev.nextInLML == null) {
+            return prev;
+        }
+        return null;
+    }
+
+    Edge getMaximaPairEx() {
+        //as above but returns null if MaxPair isn't in AEL (unless it's horizontal)
+        Edge result = getMaximaPair();
+        if (result == null || result.outIdx == Edge.SKIP ||
+          ((result.nextInAEL == result.prevInAEL) && !result.isHorizontal())) {
+        	return null;
+        }
+        return result;
+    }
+
+    public Edge getNextInAEL(Direction direction ) {
+        return direction == Direction.LEFT_TO_RIGHT ? nextInAEL : prevInAEL;
+    }
+
+    public LongPoint getTop() {
+        return top;
+    }
+
+    public boolean isContributing(PolyFillType clipFillType, PolyFillType subjFillType, ClipType clipType ) {
+        LOGGER.entering( Edge.class.getName(), "isContributing" );
+
+        PolyFillType pft, pft2;
+        if (polyTyp == PolyType.SUBJECT) {
+            pft = subjFillType;
+            pft2 = clipFillType;
+        }
+        else {
+            pft = clipFillType;
+            pft2 = subjFillType;
+        }
+
+        switch (pft) {
+            case EVEN_ODD:
+                //return false if a subj line has been flagged as inside a subj polygon
+                if (windDelta == 0 && windCnt != 1) {
+                    return false;
+                }
+                break;
+            case NON_ZERO:
+                if (Math.abs( windCnt ) != 1) {
+                    return false;
+                }
+                break;
+            case POSITIVE:
+                if (windCnt != 1) {
+                    return false;
+                }
+                break;
+            default: //PolyFillType.pftNegative
+                if (windCnt != -1) {
+                    return false;
+                }
+                break;
+        }
+
+        switch (clipType) {
+            case INTERSECTION:
+                switch (pft2) {
+                    case EVEN_ODD:
+                    case NON_ZERO:
+                        return windCnt2 != 0;
+                    case POSITIVE:
+                        return windCnt2 > 0;
+                    default:
+                        return windCnt2 < 0;
+                }
+            case UNION:
+                switch (pft2) {
+                    case EVEN_ODD:
+                    case NON_ZERO:
+                        return windCnt2 == 0;
+                    case POSITIVE:
+                        return windCnt2 <= 0;
+                    default:
+                        return windCnt2 >= 0;
+                }
+            case DIFFERENCE:
+                if (polyTyp == PolyType.SUBJECT) {
+                    switch (pft2) {
+                        case EVEN_ODD:
+                        case NON_ZERO:
+                            return windCnt2 == 0;
+                        case POSITIVE:
+                            return windCnt2 <= 0;
+                        default:
+                            return windCnt2 >= 0;
+                    }
+                }
+                else {
+                    switch (pft2) {
+                        case EVEN_ODD:
+                        case NON_ZERO:
+                            return windCnt2 != 0;
+                        case POSITIVE:
+                            return windCnt2 > 0;
+                        default:
+                            return windCnt2 < 0;
+                    }
+                }
+            case XOR:
+                if (windDelta == 0) {
+                    switch (pft2) {
+                        case EVEN_ODD:
+                        case NON_ZERO:
+                            return windCnt2 == 0;
+                        case POSITIVE:
+                            return windCnt2 <= 0;
+                        default:
+                            return windCnt2 >= 0;
+                    }
+                }
+                else {
+                    return true;
+                }
+        }
+        return true;
+    }
+
+    public boolean isEvenOddAltFillType(PolyFillType clipFillType, PolyFillType subjFillType ) {
+        if (polyTyp == PolyType.SUBJECT) {
+            return clipFillType == PolyFillType.EVEN_ODD;
+        }
+        else {
+            return subjFillType == PolyFillType.EVEN_ODD;
+        }
+    }
+
+    public boolean isEvenOddFillType(PolyFillType clipFillType, PolyFillType subjFillType ) {
+        if (polyTyp == PolyType.SUBJECT) {
+            return subjFillType == PolyFillType.EVEN_ODD;
+        }
+        else {
+            return clipFillType == PolyFillType.EVEN_ODD;
+        }
+    }
+
+    public boolean isHorizontal() {
+        return delta.getY() == 0;
+    }
+
+    public boolean isIntermediate( double y ) {
+        return top.getY() == y && nextInLML != null;
+    }
+
+    public boolean isMaxima( double Y ) {
+        return top.getY() == Y && nextInLML == null;
+    }
+
+    public void reverseHorizontal() {
+        //swap horizontal edges' top and bottom x's so they follow the natural
+        //progression of the bounds - ie so their xbots will align with the
+        //adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
+        long temp = top.getX();
+        top.setX( bot.getX() );
+        bot.setX( temp );
+
+        temp = top.getZ();
+        top.setZ( bot.getZ() );
+        bot.setZ( temp );
+
+    }
+
+    public void setBot( LongPoint bot ) {
+        this.bot.set( bot );
+    }
+
+    public void setCurrent( LongPoint current ) {
+        this.current.set( current );
+    }
+
+    public void setTop( LongPoint top ) {
+        this.top.set( top );
+    }
+
+    @Override
+    public String toString() {
+        return "TEdge [Bot=" + bot + ", Curr=" + current + ", Top=" + top + ", Delta=" + delta + ", Dx=" + deltaX + ", PolyTyp=" + polyTyp + ", Side=" + side
+                        + ", WindDelta=" + windDelta + ", WindCnt=" + windCnt + ", WindCnt2=" + windCnt2 + ", OutIdx=" + outIdx + ", Next=" + next + ", Prev="
+                        + prev + ", NextInLML=" + nextInLML + ", NextInAEL=" + nextInAEL + ", PrevInAEL=" + prevInAEL + ", NextInSEL=" + nextInSEL
+                        + ", PrevInSEL=" + prevInSEL + "]";
+    }
+
+    public void updateDeltaX() {
+
+        delta.setX( top.getX() - bot.getX() );
+        delta.setY( top.getY() - bot.getY() );
+        if (delta.getY() == 0) {
+            deltaX = Edge.HORIZONTAL;
+        }
+        else {
+            deltaX = (double) delta.getX() / delta.getY();
+        }
+    }
+
+}
diff --git a/clipper/src/main/java/de/lighti/clipper/LongRect.java b/clipper/src/main/java/de/lighti/clipper/LongRect.java
new file mode 100644
index 0000000000000000000000000000000000000000..18a10a4a877f14d20939514d1cc073d0c74d3b2d
--- /dev/null
+++ b/clipper/src/main/java/de/lighti/clipper/LongRect.java
@@ -0,0 +1,26 @@
+package de.lighti.clipper;
+
+public class LongRect {
+    public long left;
+    public long top;
+    public long right;
+    public long bottom;
+
+    public LongRect() {
+
+    }
+
+    public LongRect( long l, long t, long r, long b ) {
+        left = l;
+        top = t;
+        right = r;
+        bottom = b;
+    }
+
+    public LongRect( LongRect ir ) {
+        left = ir.left;
+        top = ir.top;
+        right = ir.right;
+        bottom = ir.bottom;
+    }
+}
diff --git a/clipper/src/main/java/de/lighti/clipper/Path.java b/clipper/src/main/java/de/lighti/clipper/Path.java
new file mode 100644
index 0000000000000000000000000000000000000000..e1ebdbf98636833d44f3e6f75b04c3cf17676b49
--- /dev/null
+++ b/clipper/src/main/java/de/lighti/clipper/Path.java
@@ -0,0 +1,414 @@
+package de.lighti.clipper;
+
+import de.lighti.clipper.Point.LongPoint;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * A pure convenience class to avoid writing List&lt;IntPoint&gt; everywhere.
+ *
+ * @author Tobias Mahlmann
+ *
+ */
+public class Path extends ArrayList<LongPoint> {
+    static class Join {
+        Path.OutPt outPt1;
+        Path.OutPt outPt2;
+        private LongPoint offPt;
+
+        public LongPoint getOffPt() {
+            return offPt;
+        }
+
+        public void setOffPt( LongPoint offPt ) {
+            this.offPt = offPt;
+        }
+
+    }
+
+    static class OutPt {
+        public static OutRec getLowerMostRec( OutRec outRec1, OutRec outRec2 ) {
+            //work out which polygon fragment has the correct hole state ...
+            if (outRec1.bottomPt == null) {
+                outRec1.bottomPt = outRec1.pts.getBottomPt();
+            }
+            if (outRec2.bottomPt == null) {
+                outRec2.bottomPt = outRec2.pts.getBottomPt();
+            }
+            final Path.OutPt bPt1 = outRec1.bottomPt;
+            final Path.OutPt bPt2 = outRec2.bottomPt;
+            if (bPt1.getPt().getY() > bPt2.getPt().getY()) {
+                return outRec1;
+            }
+            else if (bPt1.getPt().getY() < bPt2.getPt().getY()) {
+                return outRec2;
+            }
+            else if (bPt1.getPt().getX() < bPt2.getPt().getX()) {
+                return outRec1;
+            }
+            else if (bPt1.getPt().getX() > bPt2.getPt().getX()) {
+                return outRec2;
+            }
+            else if (bPt1.next == bPt1) {
+                return outRec2;
+            }
+            else if (bPt2.next == bPt2) {
+                return outRec1;
+            }
+            else if (isFirstBottomPt( bPt1, bPt2 )) {
+                return outRec1;
+            }
+            else {
+                return outRec2;
+            }
+        }
+
+        private static boolean isFirstBottomPt(Path.OutPt btmPt1, Path.OutPt btmPt2 ) {
+            Path.OutPt p = btmPt1.prev;
+            while (p.getPt().equals( btmPt1.getPt() ) && !p.equals( btmPt1 )) {
+                p = p.prev;
+            }
+            final double dx1p = Math.abs( LongPoint.getDeltaX( btmPt1.getPt(), p.getPt() ) );
+            p = btmPt1.next;
+            while (p.getPt().equals( btmPt1.getPt() ) && !p.equals( btmPt1 )) {
+                p = p.next;
+            }
+            final double dx1n = Math.abs( LongPoint.getDeltaX( btmPt1.getPt(), p.getPt() ) );
+
+            p = btmPt2.prev;
+            while (p.getPt().equals( btmPt2.getPt() ) && !p.equals( btmPt2 )) {
+                p = p.prev;
+            }
+            final double dx2p = Math.abs( LongPoint.getDeltaX( btmPt2.getPt(), p.getPt() ) );
+            p = btmPt2.next;
+            while (p.getPt().equals( btmPt2.getPt() ) && p.equals( btmPt2 )) {
+                p = p.next;
+            }
+            final double dx2n = Math.abs( LongPoint.getDeltaX( btmPt2.getPt(), p.getPt() ) );
+
+            if (Math.max( dx1p, dx1n ) == Math.max( dx2p, dx2n ) && Math.min( dx1p, dx1n ) == Math.min( dx2p, dx2n )) {
+                return btmPt1.area() > 0; //if otherwise identical use orientation
+            }
+            else {
+                return dx1p >= dx2p && dx1p >= dx2n || dx1n >= dx2p && dx1n >= dx2n;
+            }
+        }
+
+        int idx;
+        private LongPoint pt;
+        OutPt next;
+
+        OutPt prev;
+
+        public Path.OutPt duplicate(boolean InsertAfter ) {
+            final Path.OutPt result = new Path.OutPt();
+            result.setPt( new LongPoint( getPt() ) );
+            result.idx = idx;
+            if (InsertAfter) {
+                result.next = next;
+                result.prev = this;
+                next.prev = result;
+                next = result;
+            }
+            else {
+                result.prev = prev;
+                result.next = this;
+                prev.next = result;
+                prev = result;
+            }
+            return result;
+        }
+
+        Path.OutPt getBottomPt() {
+            Path.OutPt dups = null;
+            Path.OutPt p = next;
+            Path.OutPt pp = this;
+            while (p != pp) {
+                if (p.getPt().getY() > pp.getPt().getY()) {
+                    pp = p;
+                    dups = null;
+                }
+                else if (p.getPt().getY() == pp.getPt().getY() && p.getPt().getX() <= pp.getPt().getX()) {
+                    if (p.getPt().getX() < pp.getPt().getX()) {
+                        dups = null;
+                        pp = p;
+                    }
+                    else {
+                        if (p.next != pp && p.prev != pp) {
+                            dups = p;
+                        }
+                    }
+                }
+                p = p.next;
+            }
+            if (dups != null) {
+                //there appears to be at least 2 vertices at bottomPt so ...
+                while (dups != p) {
+                    if (!isFirstBottomPt( p, dups )) {
+                        pp = dups;
+                    }
+                    dups = dups.next;
+                    while (!dups.getPt().equals( pp.getPt() )) {
+                        dups = dups.next;
+                    }
+                }
+            }
+            return pp;
+        }
+
+        public static int getPointCount( OutPt pts ) {
+            if (pts == null) return 0;
+            int result = 0;
+            OutPt p = pts;
+            do {
+                result++;
+                p = p.next;
+            }
+            while (p != pts);
+            return result;
+        }
+
+        public LongPoint getPt() {
+            return pt;
+        }
+
+        public void reversePolyPtLinks() {
+
+            Path.OutPt pp1;
+            Path.OutPt pp2;
+            pp1 = this;
+            do {
+                pp2 = pp1.next;
+                pp1.next = pp1.prev;
+                pp1.prev = pp2;
+                pp1 = pp2;
+            }
+            while (pp1 != this);
+        }
+
+        public void setPt( LongPoint pt ) {
+            this.pt = pt;
+        }
+
+        private double area() {
+            Path.OutPt op = this;
+            double a = 0;
+            do {
+                a = a + (double) (op.prev.getPt().getX() + op.getPt().getX()) * (double) (op.prev.getPt().getY() - op.getPt().getY());
+                op = op.next;
+            } while (op != this);
+            return a * 0.5;
+        }
+    }
+
+    /** OutRec: contains a path in the clipping solution. Edges in the AEL will
+    carry a pointer to an OutRec when they are part of the clipping solution.*/
+    static class OutRec {
+        int Idx;
+
+        boolean isHole;
+
+        boolean isOpen;
+        OutRec firstLeft; //see comments in clipper.pas
+        private Path.OutPt pts;
+        Path.OutPt bottomPt;
+        PolyNode polyNode;
+
+        public double area() {
+            return pts.area();
+        }
+
+        public void fixHoleLinkage() {
+            //skip if an outermost polygon or
+            //already already points to the correct FirstLeft ...
+            if (firstLeft == null || isHole != firstLeft.isHole && firstLeft.pts != null) {
+                return;
+            }
+
+            OutRec orfl = firstLeft;
+            while (orfl != null && (orfl.isHole == isHole || orfl.pts == null)) {
+                orfl = orfl.firstLeft;
+            }
+            firstLeft = orfl;
+        }
+
+        public Path.OutPt getPoints() {
+            return pts;
+        }
+
+        public static OutRec parseFirstLeft( OutRec firstLeft ) {
+            while (firstLeft != null && firstLeft.getPoints() == null) {
+                firstLeft = firstLeft.firstLeft;
+            }
+            return firstLeft;
+        }
+
+        public void setPoints( Path.OutPt pts ) {
+            this.pts = pts;
+        }
+    }
+
+    private static Path.OutPt excludeOp(Path.OutPt op ) {
+        final Path.OutPt result = op.prev;
+        result.next = op.next;
+        op.next.prev = result;
+        result.idx = 0;
+        return result;
+    }
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = -7120161578077546673L;
+
+    public Path() {
+        super();
+
+    }
+
+    public Path( int cnt ) {
+        super( cnt );
+    }
+
+    public double area() {
+        final int cnt = size();
+        if (cnt < 3) {
+            return 0;
+        }
+        double a = 0;
+        for (int i = 0, j = cnt - 1; i < cnt; ++i) {
+            a += ((double) get( j ).getX() + get( i ).getX()) * ((double) get( j ).getY() - get( i ).getY());
+            j = i;
+        }
+        return -a * 0.5;
+    }
+
+    public Path cleanPolygon() {
+        return cleanPolygon( 1.415 );
+    }
+
+    public Path cleanPolygon(double distance ) {
+        //distance = proximity in units/pixels below which vertices will be stripped.
+        //Default ~= sqrt(2) so when adjacent vertices or semi-adjacent vertices have
+        //both x & y coords within 1 unit, then the second vertex will be stripped.
+
+        int cnt = size();
+
+        if (cnt == 0) {
+            return new Path();
+        }
+
+        Path.OutPt[] outPts = new Path.OutPt[cnt];
+        for (int i = 0; i < cnt; ++i) {
+            outPts[i] = new Path.OutPt();
+        }
+
+        for (int i = 0; i < cnt; ++i) {
+            outPts[i].pt = get( i );
+            outPts[i].next = outPts[(i + 1) % cnt];
+            outPts[i].next.prev = outPts[i];
+            outPts[i].idx = 0;
+        }
+
+        final double distSqrd = distance * distance;
+        Path.OutPt op = outPts[0];
+        while (op.idx == 0 && op.next != op.prev) {
+            if (Point.arePointsClose( op.pt, op.prev.pt, distSqrd )) {
+                op = excludeOp( op );
+                cnt--;
+            }
+            else if (Point.arePointsClose( op.prev.pt, op.next.pt, distSqrd )) {
+                excludeOp( op.next );
+                op = excludeOp( op );
+                cnt -= 2;
+            }
+            else if (Point.slopesNearCollinear( op.prev.pt, op.pt, op.next.pt, distSqrd )) {
+                op = excludeOp( op );
+                cnt--;
+            }
+            else {
+                op.idx = 1;
+                op = op.next;
+            }
+        }
+
+        if (cnt < 3) {
+            cnt = 0;
+        }
+        final Path result = new Path( cnt );
+        for (int i = 0; i < cnt; ++i) {
+            result.add( op.pt );
+            op = op.next;
+        }
+        outPts = null;
+        return result;
+    }
+
+    public int isPointInPolygon( LongPoint pt ) {
+        //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+        //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos
+        //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
+        int result = 0;
+        final int cnt = size();
+        if (cnt < 3) {
+            return 0;
+        }
+        LongPoint ip = get( 0 );
+        for (int i = 1; i <= cnt; ++i) {
+            final LongPoint ipNext = i == cnt ? get( 0 ) : get( i );
+            if (ipNext.getY() == pt.getY()) {
+                if (ipNext.getX() == pt.getX() || ip.getY() == pt.getY() && ipNext.getX() > pt.getX() == ip.getX() < pt.getX()) {
+                    return -1;
+                }
+            }
+            if (ip.getY() < pt.getY() != ipNext.getY() < pt.getY()) {
+                if (ip.getX() >= pt.getX()) {
+                    if (ipNext.getX() > pt.getX()) {
+                        result = 1 - result;
+                    }
+                    else {
+                        final double d = (double) (ip.getX() - pt.getX()) * (ipNext.getY() - pt.getY()) - (double) (ipNext.getX() - pt.getX())
+                                        * (ip.getY() - pt.getY());
+                        if (d == 0) {
+                            return -1;
+                        }
+                        else if (d > 0 == ipNext.getY() > ip.getY()) {
+                            result = 1 - result;
+                        }
+                    }
+                }
+                else {
+                    if (ipNext.getX() > pt.getX()) {
+                        final double d = (double) (ip.getX() - pt.getX()) * (ipNext.getY() - pt.getY()) - (double) (ipNext.getX() - pt.getX())
+                                        * (ip.getY() - pt.getY());
+                        if (d == 0) {
+                            return -1;
+                        }
+                        else if (d > 0 == ipNext.getY() > ip.getY()) {
+                            result = 1 - result;
+                        }
+                    }
+                }
+            }
+            ip = ipNext;
+        }
+        return result;
+    }
+
+    public boolean orientation() {
+        return area() >= 0;
+    }
+
+    public void reverse() {
+        Collections.reverse( this );
+    }
+
+    public Path TranslatePath(LongPoint delta ) {
+        final Path outPath = new Path( size() );
+        for (int i = 0; i < size(); i++) {
+            outPath.add( new LongPoint( get( i ).getX() + delta.getX(), get( i ).getY() + delta.getY() ) );
+        }
+        return outPath;
+    }
+}
diff --git a/clipper/src/main/java/de/lighti/clipper/Paths.java b/clipper/src/main/java/de/lighti/clipper/Paths.java
new file mode 100644
index 0000000000000000000000000000000000000000..f2a972202ee51a8d23ac6af0507fbeac239d5d72
--- /dev/null
+++ b/clipper/src/main/java/de/lighti/clipper/Paths.java
@@ -0,0 +1,125 @@
+package de.lighti.clipper;
+
+import java.util.ArrayList;
+
+/**
+ * A pure convenience class to avoid writing List&lt;Path&gt; everywhere.
+ *
+ * @author Tobias Mahlmann
+ *
+ */
+public class Paths extends ArrayList<Path> {
+
+    public static Paths closedPathsFromPolyTree(PolyTree polytree ) {
+        final Paths result = new Paths();
+        //        result.Capacity = polytree.Total;
+        result.addPolyNode( polytree, PolyNode.NodeType.CLOSED );
+        return result;
+    }
+
+    public static Paths makePolyTreeToPaths(PolyTree polytree ) {
+
+        final Paths result = new Paths();
+        //        result.Capacity = polytree.Total;
+        result.addPolyNode( polytree, PolyNode.NodeType.ANY );
+        return result;
+    }
+
+    public static Paths openPathsFromPolyTree(PolyTree polytree ) {
+        final Paths result = new Paths();
+        //        result.Capacity = polytree.ChildCount;
+        for (final PolyNode c : polytree.getChilds()) {
+            if (c.isOpen()) {
+                result.add( c.getPolygon() );
+            }
+        }
+        return result;
+    }
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1910552127810480852L;
+
+    public Paths() {
+        super();
+    }
+
+    public Paths( int initialCapacity ) {
+        super( initialCapacity );
+    }
+
+    public void addPolyNode(PolyNode polynode, PolyNode.NodeType nt ) {
+        boolean match = true;
+        switch (nt) {
+            case OPEN:
+                return;
+            case CLOSED:
+                match = !polynode.isOpen();
+                break;
+            default:
+                break;
+        }
+
+        if (polynode.getPolygon().size() > 0 && match) {
+            add( polynode.getPolygon() );
+        }
+        for (final PolyNode pn : polynode.getChilds()) {
+            addPolyNode( pn, nt );
+        }
+    }
+
+    public Paths cleanPolygons() {
+        return cleanPolygons( 1.415 );
+    }
+
+    public Paths cleanPolygons(double distance ) {
+        final Paths result = new Paths( size() );
+        for (int i = 0; i < size(); i++) {
+            result.add( get( i ).cleanPolygon( distance ) );
+        }
+        return result;
+    }
+
+    public LongRect getBounds() {
+
+        int i = 0;
+        final int cnt = size();
+        final LongRect result = new LongRect();
+        while (i < cnt && get( i ).isEmpty()) {
+            i++;
+        }
+        if (i == cnt) {
+            return result;
+        }
+
+        result.left = get( i ).get( 0 ).getX();
+        result.right = result.left;
+        result.top = get( i ).get( 0 ).getY();
+        result.bottom = result.top;
+        for (; i < cnt; i++) {
+            for (int j = 0; j < get( i ).size(); j++) {
+                if (get( i ).get( j ).getX() < result.left) {
+                    result.left = get( i ).get( j ).getX();
+                }
+                else if (get( i ).get( j ).getX() > result.right) {
+                    result.right = get( i ).get( j ).getX();
+                }
+                if (get( i ).get( j ).getY() < result.top) {
+                    result.top = get( i ).get( j ).getY();
+                }
+                else if (get( i ).get( j ).getY() > result.bottom) {
+                    result.bottom = get( i ).get( j ).getY();
+                }
+            }
+        }
+        return result;
+    }
+
+    public void reversePaths() {
+        for (final Path poly : this) {
+            poly.reverse();
+        }
+    }
+
+}
diff --git a/clipper/src/main/java/de/lighti/clipper/Point.java b/clipper/src/main/java/de/lighti/clipper/Point.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ba3b31d6a30c7dff43454c0b1f2b406f0ef9164
--- /dev/null
+++ b/clipper/src/main/java/de/lighti/clipper/Point.java
@@ -0,0 +1,222 @@
+package de.lighti.clipper;
+
+import java.util.Comparator;
+
+public abstract class Point<T extends Number & Comparable<T>> {
+    public static class DoublePoint extends Point<Double> {
+        public DoublePoint() {
+            this( 0, 0 );
+        }
+
+        public DoublePoint( double x, double y ) {
+            this( x, y, 0 );
+        }
+
+        public DoublePoint( double x, double y, double z ) {
+            super( x, y, z );
+        }
+
+        public DoublePoint( DoublePoint other ) {
+            super( other );
+        }
+
+        public double getX() {
+            return x;
+        }
+
+        public double getY() {
+            return y;
+        }
+
+        public double getZ() {
+            return z;
+        }
+    }
+
+    public static class LongPoint extends Point<Long> {
+        public static double getDeltaX(LongPoint pt1, LongPoint pt2 ) {
+            if (pt1.getY() == pt2.getY()) {
+                return Edge.HORIZONTAL;
+            }
+            else {
+                return (double) (pt2.getX() - pt1.getX()) / (pt2.getY() - pt1.getY());
+            }
+        }
+
+        public LongPoint() {
+            this( 0, 0 );
+        }
+
+        public LongPoint( long x, long y ) {
+            this( x, y, 0 );
+        }
+
+        public LongPoint( long x, long y, long z ) {
+            super( x, y, z );
+        }
+
+        public LongPoint( LongPoint other ) {
+            super( other );
+        }
+
+        public long getX() {
+            return x;
+        }
+
+        public long getY() {
+            return y;
+        }
+
+        public long getZ() {
+            return z;
+        }
+    }
+
+    private static class NumberComparator<T extends Number & Comparable<T>> implements Comparator<T> {
+
+        @Override
+        public int compare( T a, T b ) throws ClassCastException {
+            return a.compareTo( b );
+        }
+    }
+
+    static boolean arePointsClose(Point<? extends Number> pt1, Point<? extends Number> pt2, double distSqrd ) {
+        final double dx = pt1.x.doubleValue() - pt2.x.doubleValue();
+        final double dy = pt1.y.doubleValue() - pt2.y.doubleValue();
+        return dx * dx + dy * dy <= distSqrd;
+    }
+
+    static double distanceFromLineSqrd(Point<? extends Number> pt, Point<? extends Number> ln1, Point<? extends Number> ln2 ) {
+        //The equation of a line in general form (Ax + By + C = 0)
+        //given 2 points (x¹,y¹) & (x²,y²) is ...
+        //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0
+        //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹
+        //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²)
+        //see http://en.wikipedia.org/wiki/Perpendicular_distance
+        final double A = ln1.y.doubleValue() - ln2.y.doubleValue();
+        final double B = ln2.x.doubleValue() - ln1.x.doubleValue();
+        double C = A * ln1.x.doubleValue() + B * ln1.y.doubleValue();
+        C = A * pt.x.doubleValue() + B * pt.y.doubleValue() - C;
+        return C * C / (A * A + B * B);
+    }
+
+    static DoublePoint getUnitNormal( LongPoint pt1, LongPoint pt2 ) {
+        double dx = pt2.x - pt1.x;
+        double dy = pt2.y - pt1.y;
+        if (dx == 0 && dy == 0) {
+            return new DoublePoint();
+        }
+
+        final double f = 1 * 1.0 / Math.sqrt( dx * dx + dy * dy );
+        dx *= f;
+        dy *= f;
+
+        return new DoublePoint( dy, -dx );
+    }
+
+    protected static boolean isPt2BetweenPt1AndPt3( LongPoint pt1, LongPoint pt2, LongPoint pt3 ) {
+        if (pt1.equals( pt3 ) || pt1.equals( pt2 ) || pt3.equals( pt2 )) {
+            return false;
+        }
+        else if (pt1.x != pt3.x) {
+            return pt2.x > pt1.x == pt2.x < pt3.x;
+        }
+        else {
+            return pt2.y > pt1.y == pt2.y < pt3.y;
+        }
+    }
+
+    protected static boolean slopesEqual( LongPoint pt1, LongPoint pt2, LongPoint pt3 ) {
+        return (pt1.y - pt2.y) * (pt2.x - pt3.x) - (pt1.x - pt2.x) * (pt2.y - pt3.y) == 0;
+    }
+
+    protected static boolean slopesEqual( LongPoint pt1, LongPoint pt2, LongPoint pt3, LongPoint pt4 ) {
+        return (pt1.y - pt2.y) * (pt3.x - pt4.x) - (pt1.x - pt2.x) * (pt3.y - pt4.y) == 0;
+    }
+
+    static boolean slopesNearCollinear( LongPoint pt1, LongPoint pt2, LongPoint pt3, double distSqrd ) {
+        //this function is more accurate when the point that's GEOMETRICALLY
+        //between the other 2 points is the one that's tested for distance.
+        //nb: with 'spikes', either pt1 or pt3 is geometrically between the other pts
+        if (Math.abs( pt1.x - pt2.x ) > Math.abs( pt1.y - pt2.y )) {
+            if (pt1.x > pt2.x == pt1.x < pt3.x) {
+                return distanceFromLineSqrd( pt1, pt2, pt3 ) < distSqrd;
+            }
+            else if (pt2.x > pt1.x == pt2.x < pt3.x) {
+                return distanceFromLineSqrd( pt2, pt1, pt3 ) < distSqrd;
+            }
+            else {
+                return distanceFromLineSqrd( pt3, pt1, pt2 ) < distSqrd;
+            }
+        }
+        else {
+            if (pt1.y > pt2.y == pt1.y < pt3.y) {
+                return distanceFromLineSqrd( pt1, pt2, pt3 ) < distSqrd;
+            }
+            else if (pt2.y > pt1.y == pt2.y < pt3.y) {
+                return distanceFromLineSqrd( pt2, pt1, pt3 ) < distSqrd;
+            }
+            else {
+                return distanceFromLineSqrd( pt3, pt1, pt2 ) < distSqrd;
+            }
+        }
+    }
+
+    @SuppressWarnings("rawtypes")
+    private final static NumberComparator NUMBER_COMPARATOR = new NumberComparator();
+
+    protected T x;
+
+    protected T y;
+
+    protected T z;
+
+    protected Point( Point<T> pt ) {
+        this( pt.x, pt.y, pt.z );
+    }
+
+    protected Point( T x, T y, T z ) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public boolean equals( Object obj ) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof Point<?>) {
+            final Point<?> a = (Point<?>) obj;
+            return NUMBER_COMPARATOR.compare( x, a.x ) == 0 && NUMBER_COMPARATOR.compare( y, a.y ) == 0;
+        }
+        else {
+            return false;
+        }
+    }
+
+    public void set( Point<T> other ) {
+        x = other.x;
+        y = other.y;
+        z = other.z;
+    }
+
+    public void setX( T x ) {
+        this.x = x;
+    }
+
+    public void setY( T y ) {
+        this.y = y;
+    }
+
+    public void setZ( T z ) {
+        this.z = z;
+    }
+
+    @Override
+    public String toString() {
+        return "Point [x=" + x + ", y=" + y + ", z=" + z + "]";
+    }
+
+}// end struct IntPoint
\ No newline at end of file
diff --git a/clipper/src/main/java/de/lighti/clipper/PolyNode.java b/clipper/src/main/java/de/lighti/clipper/PolyNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..085b022c6eb39186f750f18ef12a4d073f3cc962
--- /dev/null
+++ b/clipper/src/main/java/de/lighti/clipper/PolyNode.java
@@ -0,0 +1,116 @@
+package de.lighti.clipper;
+
+import de.lighti.clipper.Clipper.EndType;
+import de.lighti.clipper.Clipper.JoinType;
+import de.lighti.clipper.Point.LongPoint;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class PolyNode {
+
+    enum NodeType {
+        ANY, OPEN, CLOSED
+    }
+
+    private PolyNode parent;
+    private final Path polygon = new Path();
+    private int index;
+    private JoinType joinType;
+    private EndType endType;
+    final List<PolyNode> childs = new ArrayList<>();
+    private boolean isOpen;
+
+    public void addChild( PolyNode child ) {
+        final int cnt = childs.size();
+        childs.add( child );
+        child.parent = this;
+        child.index = cnt;
+    }
+
+    public int getChildCount() {
+        return childs.size();
+    }
+
+    public List<PolyNode> getChilds() {
+        return Collections.unmodifiableList( childs );
+    }
+
+    public List<LongPoint> getContour() {
+        return polygon;
+    }
+
+    public EndType getEndType() {
+        return endType;
+    }
+
+    public JoinType getJoinType() {
+        return joinType;
+    }
+
+    public PolyNode getNext() {
+        if (!childs.isEmpty()) {
+            return childs.get( 0 );
+        }
+        else {
+            return getNextSiblingUp();
+        }
+    }
+
+    private PolyNode getNextSiblingUp() {
+        if (parent == null) {
+            return null;
+        }
+        else if (index == parent.childs.size() - 1) {
+            return parent.getNextSiblingUp();
+        }
+        else {
+            return parent.childs.get( index + 1 );
+        }
+    }
+
+    public PolyNode getParent() {
+        return parent;
+    }
+
+    public Path getPolygon() {
+        return polygon;
+    }
+
+    public boolean isHole() {
+        return isHoleNode();
+    }
+
+    private boolean isHoleNode() {
+        boolean result = true;
+        PolyNode node = parent;
+        while (node != null) {
+            result = !result;
+            node = node.parent;
+        }
+        return result;
+    }
+
+    public boolean isOpen() {
+        return isOpen;
+    }
+
+    public void setEndType( EndType value ) {
+        endType = value;
+    }
+
+    public void setJoinType( JoinType value ) {
+        joinType = value;
+    }
+
+    public void setOpen( boolean isOpen ) {
+        this.isOpen = isOpen;
+    }
+
+    public void setParent( PolyNode n ) {
+        parent = n;
+
+    }
+
+}
diff --git a/clipper/src/main/java/de/lighti/clipper/PolyTree.java b/clipper/src/main/java/de/lighti/clipper/PolyTree.java
new file mode 100644
index 0000000000000000000000000000000000000000..b0c191b289746de1bcadc0bf6b4d3d538a2d53cc
--- /dev/null
+++ b/clipper/src/main/java/de/lighti/clipper/PolyTree.java
@@ -0,0 +1,37 @@
+package de.lighti.clipper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PolyTree extends PolyNode {
+    private final List<PolyNode> allPolys = new ArrayList<PolyNode>();
+
+    public void Clear() {
+        allPolys.clear();
+        childs.clear();
+    }
+
+    public List<PolyNode> getAllPolys() {
+        return allPolys;
+    }
+
+    public PolyNode getFirst() {
+        if (!childs.isEmpty()) {
+            return childs.get( 0 );
+        }
+        else {
+            return null;
+        }
+    }
+
+    public int getTotalSize() {
+        int result = allPolys.size();
+        //with negative offsets, ignore the hidden outer polygon ...
+        if (result > 0 && childs.get( 0 ) != allPolys.get( 0 )) {
+            result--;
+        }
+        return result;
+
+    }
+
+}
diff --git a/clipper/src/main/java/plugins/danyfel80/library/clipper/ClipperPlugin.java b/clipper/src/main/java/plugins/danyfel80/library/clipper/ClipperPlugin.java
new file mode 100644
index 0000000000000000000000000000000000000000..91d0df5570fcfb2b1fbc96e6d463b1b0d7791950
--- /dev/null
+++ b/clipper/src/main/java/plugins/danyfel80/library/clipper/ClipperPlugin.java
@@ -0,0 +1,12 @@
+package plugins.danyfel80.library.clipper;
+import icy.plugin.abstract_.Plugin;
+import icy.plugin.interface_.PluginLibrary;
+
+/**
+ * Icy wrapper for the Clipper library made by de.lighti.
+ * @author Daniel Felipe Gonzalez Obando
+ */
+public class ClipperPlugin extends Plugin implements PluginLibrary
+{
+
+}