/* * * This file is part of the iText (R) project. * Copyright (c) 2014-2015 iText Group NV * Authors: Bruno Lowagie, Paulo Soares, et al. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT * OF THIRD PARTY RIGHTS * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA, 02110-1301 USA, or download the license from the following URL: * http://itextpdf.com/terms-of-use/ * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * In accordance with Section 7(b) of the GNU Affero General Public License, * a covered work must retain the producer line in every PDF that is created * or manipulated using iText. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the iText software without * disclosing the source code of your own applications. * These activities include: offering paid services to customers as an ASP, * serving PDFs on the fly in a web application, shipping iText with a closed * source product. * * For more information, please contact iText Software Corp. at this * address: sales@itextpdf.com * * * This class is based on the C# open source freeware library Clipper: * http://www.angusj.com/delphi/clipper.php * The original classes were distributed under the Boost Software License: * * Freeware for both open source and commercial applications * Copyright 2010-2014 Angus Johnson * Boost Software License - Version 1.0 - August 17th, 2003 * * Permission is hereby granted, free of charge, to any person or organization * obtaining a copy of the software and accompanying documentation covered by * this license (the "Software") to use, reproduce, display, distribute, * execute, and transmit the Software, and to prepare derivative works of the * Software, and to permit third-parties to whom the Software is furnished to * do so, all subject to the following: * * The copyright notices in the Software and this entire statement, including * the above license grant, this restriction and the following disclaimer, * must be included in all copies of the Software, in whole or in part, and * all derivative works of the Software, unless such copies or derivative * works are solely in the form of machine-executable object code generated by * a source language processor. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package com.itextpdf.text.pdf.parser.clipper; import com.itextpdf.text.pdf.parser.clipper.Path.Join; import com.itextpdf.text.pdf.parser.clipper.Path.OutRec; import com.itextpdf.text.pdf.parser.clipper.Point.LongPoint; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.logging.Logger; public class DefaultClipper extends ClipperBase { private class IntersectNode { Edge edge1; Edge Edge2; private LongPoint pt; public LongPoint getPt() { return pt; } public void setPt( LongPoint pt ) { this.pt = pt; } }; private static void getHorzDirection( Edge HorzEdge, Direction[] Dir, long[] Left, long[] Right ) { if (HorzEdge.getBot().getX() < HorzEdge.getTop().getX()) { Left[0] = HorzEdge.getBot().getX(); Right[0] = HorzEdge.getTop().getX(); Dir[0] = Direction.LEFT_TO_RIGHT; } else { Left[0] = HorzEdge.getTop().getX(); Right[0] = HorzEdge.getBot().getX(); Dir[0] = Direction.RIGHT_TO_LEFT; } } private static boolean getOverlap( long a1, long a2, long b1, long b2, long[] Left, long[] Right ) { if (a1 < a2) { if (b1 < b2) { Left[0] = Math.max( a1, b1 ); Right[0] = Math.min( a2, b2 ); } else { Left[0] = Math.max( a1, b2 ); Right[0] = Math.min( a2, b1 ); } } else { if (b1 < b2) { Left[0] = Math.max( a2, b1 ); Right[0] = Math.min( a1, b2 ); } else { Left[0] = Math.max( a2, b2 ); Right[0] = Math.min( a1, b1 ); } } return Left[0] < Right[0]; } private static boolean isParam1RightOfParam2( OutRec outRec1, OutRec outRec2 ) { do { outRec1 = outRec1.firstLeft; if (outRec1 == outRec2) { return true; } } while (outRec1 != null); return false; } //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf private static int isPointInPolygon( LongPoint pt, Path.OutPt op ) { //returns 0 if false, +1 if true, -1 if pt ON polygon boundary int result = 0; final Path.OutPt startOp = op; final long ptx = pt.getX(), pty = pt.getY(); long poly0x = op.getPt().getX(), poly0y = op.getPt().getY(); do { op = op.next; final long poly1x = op.getPt().getX(), poly1y = op.getPt().getY(); if (poly1y == pty) { if (poly1x == ptx || poly0y == pty && poly1x > ptx == poly0x < ptx) { return -1; } } if (poly0y < pty != poly1y < pty) { if (poly0x >= ptx) { if (poly1x > ptx) { result = 1 - result; } else { final double d = (double) (poly0x - ptx) * (poly1y - pty) - (double) (poly1x - ptx) * (poly0y - pty); if (d == 0) { return -1; } if (d > 0 == poly1y > poly0y) { result = 1 - result; } } } else { if (poly1x > ptx) { final double d = (double) (poly0x - ptx) * (poly1y - pty) - (double) (poly1x - ptx) * (poly0y - pty); if (d == 0) { return -1; } if (d > 0 == poly1y > poly0y) { result = 1 - result; } } } } poly0x = poly1x; poly0y = poly1y; } while (startOp != op); return result; } //------------------------------------------------------------------------------ private static boolean joinHorz( Path.OutPt op1, Path.OutPt op1b, Path.OutPt op2, Path.OutPt op2b, LongPoint Pt, boolean DiscardLeft ) { final Direction Dir1 = op1.getPt().getX() > op1b.getPt().getX() ? Direction.RIGHT_TO_LEFT : Direction.LEFT_TO_RIGHT; final Direction Dir2 = op2.getPt().getX() > op2b.getPt().getX() ? Direction.RIGHT_TO_LEFT : Direction.LEFT_TO_RIGHT; if (Dir1 == Dir2) { return false; } //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) //So, to facilitate this while inserting Op1b and Op2b ... //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) if (Dir1 == Direction.LEFT_TO_RIGHT) { while (op1.next.getPt().getX() <= Pt.getX() && op1.next.getPt().getX() >= op1.getPt().getX() && op1.next.getPt().getY() == Pt.getY()) { op1 = op1.next; } if (DiscardLeft && op1.getPt().getX() != Pt.getX()) { op1 = op1.next; } op1b = op1.duplicate( !DiscardLeft ); if (!op1b.getPt().equals( Pt )) { op1 = op1b; op1.setPt( Pt ); op1b = op1.duplicate( !DiscardLeft ); } } else { while (op1.next.getPt().getX() >= Pt.getX() && op1.next.getPt().getX() <= op1.getPt().getX() && op1.next.getPt().getY() == Pt.getY()) { op1 = op1.next; } if (!DiscardLeft && op1.getPt().getX() != Pt.getX()) { op1 = op1.next; } op1b = op1.duplicate( DiscardLeft ); if (!op1b.getPt().equals( Pt )) { op1 = op1b; op1.setPt( Pt ); op1b = op1.duplicate( DiscardLeft ); } } if (Dir2 == Direction.LEFT_TO_RIGHT) { while (op2.next.getPt().getX() <= Pt.getX() && op2.next.getPt().getX() >= op2.getPt().getX() && op2.next.getPt().getY() == Pt.getY()) { op2 = op2.next; } if (DiscardLeft && op2.getPt().getX() != Pt.getX()) { op2 = op2.next; } op2b = op2.duplicate( !DiscardLeft ); if (!op2b.getPt().equals( Pt )) { op2 = op2b; op2.setPt( Pt ); op2b = op2.duplicate( !DiscardLeft ); } ; } else { while (op2.next.getPt().getX() >= Pt.getX() && op2.next.getPt().getX() <= op2.getPt().getX() && op2.next.getPt().getY() == Pt.getY()) { op2 = op2.next; } if (!DiscardLeft && op2.getPt().getX() != Pt.getX()) { op2 = op2.next; } op2b = op2.duplicate( DiscardLeft ); if (!op2b.getPt().equals( Pt )) { op2 = op2b; op2.setPt( Pt ); op2b = op2.duplicate( DiscardLeft ); } ; } ; if (Dir1 == Direction.LEFT_TO_RIGHT == DiscardLeft) { op1.prev = op2; op2.next = op1; op1b.next = op2b; op2b.prev = op1b; } else { op1.next = op2; op2.prev = op1; op1b.prev = op2b; op2b.next = op1b; } return true; } private boolean joinPoints( Join j, OutRec outRec1, OutRec outRec2 ) { Path.OutPt op1 = j.outPt1, op1b; Path.OutPt op2 = j.outPt2, op2b; //There are 3 kinds of joins for output polygons ... //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are a vertices anywhere //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same //location at the Bottom of the overlapping segment (& Join.OffPt is above). //3. StrictlySimple joins where edges touch but are not collinear and where //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. final boolean isHorizontal = j.outPt1.getPt().getY() == j.getOffPt().getY(); if (isHorizontal && j.getOffPt().equals( j.outPt1.getPt() ) && j.getOffPt().equals( j.outPt2.getPt() )) { //Strictly Simple join ... if (outRec1 != outRec2) { return false; } op1b = j.outPt1.next; while (op1b != op1 && op1b.getPt().equals( j.getOffPt() )) { op1b = op1b.next; } final boolean reverse1 = op1b.getPt().getY() > j.getOffPt().getY(); op2b = j.outPt2.next; while (op2b != op2 && op2b.getPt().equals( j.getOffPt() )) { op2b = op2b.next; } final boolean reverse2 = op2b.getPt().getY() > j.getOffPt().getY(); if (reverse1 == reverse2) { return false; } if (reverse1) { op1b = op1.duplicate( false ); op2b = op2.duplicate( true ); op1.prev = op2; op2.next = op1; op1b.next = op2b; op2b.prev = op1b; j.outPt1 = op1; j.outPt2 = op1b; return true; } else { op1b = op1.duplicate( true ); op2b = op2.duplicate( false ); op1.next = op2; op2.prev = op1; op1b.prev = op2b; op2b.next = op1b; j.outPt1 = op1; j.outPt2 = op1b; return true; } } else if (isHorizontal) { //treat horizontal joins differently to non-horizontal joins since with //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt //may be anywhere along the horizontal edge. op1b = op1; while (op1.prev.getPt().getY() == op1.getPt().getY() && op1.prev != op1b && op1.prev != op2) { op1 = op1.prev; } while (op1b.next.getPt().getY() == op1b.getPt().getY() && op1b.next != op1 && op1b.next != op2) { op1b = op1b.next; } if (op1b.next == op1 || op1b.next == op2) { return false; } //a flat 'polygon' op2b = op2; while (op2.prev.getPt().getY() == op2.getPt().getY() && op2.prev != op2b && op2.prev != op1b) { op2 = op2.prev; } while (op2b.next.getPt().getY() == op2b.getPt().getY() && op2b.next != op2 && op2b.next != op1) { op2b = op2b.next; } if (op2b.next == op2 || op2b.next == op1) { return false; } //a flat 'polygon' final long[] LeftV = new long[1], RightV = new long[1]; //Op1 -. Op1b & Op2 -. Op2b are the extremites of the horizontal edges if (!getOverlap( op1.getPt().getX(), op1b.getPt().getX(), op2.getPt().getX(), op2b.getPt().getX(), LeftV, RightV )) { return false; } final long Left = LeftV[0]; final long Right = RightV[0]; //DiscardLeftSide: when overlapping edges are joined, a spike will created //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up //on the discard Side as either may still be needed for other joins ... LongPoint Pt; boolean DiscardLeftSide; if (op1.getPt().getX() >= Left && op1.getPt().getX() <= Right) { Pt = new LongPoint( op1.getPt() ); DiscardLeftSide = op1.getPt().getX() > op1b.getPt().getX(); } else if (op2.getPt().getX() >= Left && op2.getPt().getX() <= Right) { Pt = new LongPoint( op2.getPt() ); DiscardLeftSide = op2.getPt().getX() > op2b.getPt().getX(); } else if (op1b.getPt().getX() >= Left && op1b.getPt().getX() <= Right) { Pt = new LongPoint( op1b.getPt() ); DiscardLeftSide = op1b.getPt().getX() > op1.getPt().getX(); } else { Pt = new LongPoint( op2b.getPt() ); DiscardLeftSide = op2b.getPt().getX() > op2.getPt().getX(); } j.outPt1 = op1; j.outPt2 = op2; return joinHorz( op1, op1b, op2, op2b, Pt, DiscardLeftSide ); } else { //nb: For non-horizontal joins ... // 1. Jr.OutPt1.getPt().getY() == Jr.OutPt2.getPt().getY() // 2. Jr.OutPt1.Pt > Jr.OffPt.getY() //make sure the polygons are correctly oriented ... op1b = op1.next; while (op1b.getPt().equals( op1.getPt() ) && op1b != op1) { op1b = op1b.next; } final boolean Reverse1 = op1b.getPt().getY() > op1.getPt().getY() || !Point.slopesEqual( op1.getPt(), op1b.getPt(), j.getOffPt(), useFullRange ); if (Reverse1) { op1b = op1.prev; while (op1b.getPt().equals( op1.getPt() ) && op1b != op1) { op1b = op1b.prev; } if (op1b.getPt().getY() > op1.getPt().getY() || !Point.slopesEqual( op1.getPt(), op1b.getPt(), j.getOffPt(), useFullRange )) { return false; } } ; op2b = op2.next; while (op2b.getPt().equals( op2.getPt() ) && op2b != op2) { op2b = op2b.next; } final boolean Reverse2 = op2b.getPt().getY() > op2.getPt().getY() || !Point.slopesEqual( op2.getPt(), op2b.getPt(), j.getOffPt(), useFullRange ); if (Reverse2) { op2b = op2.prev; while (op2b.getPt().equals( op2.getPt() ) && op2b != op2) { op2b = op2b.prev; } if (op2b.getPt().getY() > op2.getPt().getY() || !Point.slopesEqual( op2.getPt(), op2b.getPt(), j.getOffPt(), useFullRange )) { return false; } } if (op1b == op1 || op2b == op2 || op1b == op2b || outRec1 == outRec2 && Reverse1 == Reverse2) { return false; } if (Reverse1) { op1b = op1.duplicate( false ); op2b = op2.duplicate( true ); op1.prev = op2; op2.next = op1; op1b.next = op2b; op2b.prev = op1b; j.outPt1 = op1; j.outPt2 = op1b; return true; } else { op1b = op1.duplicate( true ); op2b = op2.duplicate( false ); op1.next = op2; op2.prev = op1; op1b.prev = op2b; op2b.next = op1b; j.outPt1 = op1; j.outPt2 = op1b; return true; } } } private static Paths minkowski( Path pattern, Path path, boolean IsSum, boolean IsClosed ) { final int delta = IsClosed ? 1 : 0; final int polyCnt = pattern.size(); final int pathCnt = path.size(); final Paths result = new Paths( pathCnt ); if (IsSum) { for (int i = 0; i < pathCnt; i++) { final Path p = new Path( polyCnt ); for (final LongPoint ip : pattern) { p.add( new LongPoint( path.get( i ).getX() + ip.getX(), path.get( i ).getY() + ip.getY(), 0 ) ); } result.add( p ); } } else { for (int i = 0; i < pathCnt; i++) { final Path p = new Path( polyCnt ); for (final LongPoint ip : pattern) { p.add( new LongPoint( path.get( i ).getX() - ip.getX(), path.get( i ).getY() - ip.getY(), 0 ) ); } result.add( p ); } } final Paths quads = new Paths( (pathCnt + delta) * (polyCnt + 1) ); for (int i = 0; i < pathCnt - 1 + delta; i++) { for (int j = 0; j < polyCnt; j++) { final Path quad = new Path( 4 ); quad.add( result.get( i % pathCnt ).get( j % polyCnt ) ); quad.add( result.get( (i + 1) % pathCnt ).get( j % polyCnt ) ); quad.add( result.get( (i + 1) % pathCnt ).get( (j + 1) % polyCnt ) ); quad.add( result.get( i % pathCnt ).get( (j + 1) % polyCnt ) ); if (!quad.orientation()) { Collections.reverse( quad ); } quads.add( quad ); } } return quads; } public static Paths minkowskiDiff( Path poly1, Path poly2 ) { final Paths paths = minkowski( poly1, poly2, false, true ); final DefaultClipper c = new DefaultClipper(); c.addPaths( paths, PolyType.SUBJECT, true ); c.execute( ClipType.UNION, paths, PolyFillType.NON_ZERO, PolyFillType.NON_ZERO ); return paths; } public static Paths minkowskiSum( Path pattern, Path path, boolean pathIsClosed ) { final Paths paths = minkowski( pattern, path, true, pathIsClosed ); final DefaultClipper c = new DefaultClipper(); c.addPaths( paths, PolyType.SUBJECT, true ); c.execute( ClipType.UNION, paths, PolyFillType.NON_ZERO, PolyFillType.NON_ZERO ); return paths; } public static Paths minkowskiSum( Path pattern, Paths paths, boolean pathIsClosed ) { final Paths solution = new Paths(); final DefaultClipper c = new DefaultClipper(); for (int i = 0; i < paths.size(); ++i) { final Paths tmp = minkowski( pattern, paths.get( i ), true, pathIsClosed ); c.addPaths( tmp, PolyType.SUBJECT, true ); if (pathIsClosed) { final Path path = paths.get( i ).TranslatePath( pattern.get( 0 ) ); c.addPath( path, PolyType.CLIP, true ); } } c.execute( ClipType.UNION, solution, PolyFillType.NON_ZERO, PolyFillType.NON_ZERO ); return solution; } private static boolean poly2ContainsPoly1( Path.OutPt outPt1, Path.OutPt outPt2 ) { Path.OutPt op = outPt1; do { //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon final int res = isPointInPolygon( op.getPt(), outPt2 ); if (res >= 0) { return res > 0; } op = op.next; } while (op != outPt1); return true; } //------------------------------------------------------------------------------ // SimplifyPolygon functions ... // Convert self-intersecting polygons into simple polygons //------------------------------------------------------------------------------ public static Paths simplifyPolygon( Path poly ) { return simplifyPolygon( poly, PolyFillType.EVEN_ODD ); } public static Paths simplifyPolygon( Path poly, PolyFillType fillType ) { final Paths result = new Paths(); final DefaultClipper c = new DefaultClipper( STRICTLY_SIMPLE ); c.addPath( poly, PolyType.SUBJECT, true ); c.execute( ClipType.UNION, result, fillType, fillType ); return result; } public static Paths simplifyPolygons( Paths polys ) { return simplifyPolygons( polys, PolyFillType.EVEN_ODD ); } public static Paths simplifyPolygons( Paths polys, PolyFillType fillType ) { final Paths result = new Paths(); final DefaultClipper c = new DefaultClipper( STRICTLY_SIMPLE ); c.addPaths( polys, PolyType.SUBJECT, true ); c.execute( ClipType.UNION, result, fillType, fillType ); return result; } protected final List<OutRec> polyOuts; private ClipType clipType; private Scanbeam scanbeam; private Path.Maxima maxima; private Edge activeEdges; private Edge sortedEdges; private final List<IntersectNode> intersectList; private final Comparator<IntersectNode> intersectNodeComparer; private PolyFillType clipFillType; //------------------------------------------------------------------------------ private PolyFillType subjFillType; //------------------------------------------------------------------------------ private final List<Join> joins; //------------------------------------------------------------------------------ private final List<Join> ghostJoins; private boolean usingPolyTree; public ZFillCallback zFillFunction; //------------------------------------------------------------------------------ private final boolean reverseSolution; //------------------------------------------------------------------------------ private final boolean strictlySimple; private final static Logger LOGGER = Logger.getLogger( DefaultClipper.class.getName() ); public DefaultClipper() { this( 0 ); } public DefaultClipper( int InitOptions ) //constructor { super( (PRESERVE_COLINEAR & InitOptions) != 0 ); scanbeam = null; maxima = null; activeEdges = null; sortedEdges = null; intersectList = new ArrayList<IntersectNode>(); intersectNodeComparer = new Comparator<IntersectNode>() { public int compare(IntersectNode o1, IntersectNode o2) { final long i = o2.getPt().getY() - o1.getPt().getY(); if (i > 0) { return 1; } else if (i < 0) { return -1; } else { return 0; } } }; usingPolyTree = false; polyOuts = new ArrayList<OutRec>(); joins = new ArrayList<Join>(); ghostJoins = new ArrayList<Join>(); reverseSolution = (REVERSE_SOLUTION & InitOptions) != 0; strictlySimple = (STRICTLY_SIMPLE & InitOptions) != 0; zFillFunction = null; } private void insertScanbeam(long Y) { //single-linked list: sorted descending, ignoring dups. if (scanbeam == null) { scanbeam = new Scanbeam(); scanbeam.next = null; scanbeam.y = Y; } else if (Y > scanbeam.y) { Scanbeam newSb = new Scanbeam(); newSb.y = Y; newSb.next = scanbeam; scanbeam = newSb; } else { Scanbeam sb2 = scanbeam; while (sb2.next != null && (Y <= sb2.next.y)) sb2 = sb2.next; if (Y == sb2.y) return; //ie ignores duplicates Scanbeam newSb = new Scanbeam(); newSb.y = Y; newSb.next = sb2.next; sb2.next = newSb; } } //------------------------------------------------------------------------------ private void InsertMaxima(long X) { //double-linked list: sorted ascending, ignoring dups. Path.Maxima newMax = new Path.Maxima(); newMax.X = X; if (maxima == null) { maxima = newMax; maxima.Next = null; maxima.Prev = null; } else if (X < maxima.X) { newMax.Next = maxima; newMax.Prev = null; maxima = newMax; } else { Path.Maxima m = maxima; while (m.Next != null && (X >= m.Next.X)) m = m.Next; if (X == m.X) return; //ie ignores duplicates (& CG to clean up newMax) //insert newMax between m and m.Next ... newMax.Next = m.Next; newMax.Prev = m; if (m.Next != null) m.Next.Prev = newMax; m.Next = newMax; } } //------------------------------------------------------------------------------ private void addEdgeToSEL( Edge edge ) { LOGGER.entering( DefaultClipper.class.getName(), "addEdgeToSEL" ); //SEL pointers in PEdge are reused to build a list of horizontal edges. //However, we don't need to worry about order with horizontal edge processing. if (sortedEdges == null) { sortedEdges = edge; edge.prevInSEL = null; edge.nextInSEL = null; } else { edge.nextInSEL = sortedEdges; edge.prevInSEL = null; sortedEdges.prevInSEL = edge; sortedEdges = edge; } } private void addGhostJoin( Path.OutPt Op, LongPoint OffPt ) { final Join j = new Join(); j.outPt1 = Op; j.setOffPt( OffPt ); ghostJoins.add( j ); } //------------------------------------------------------------------------------ private void addJoin( Path.OutPt Op1, Path.OutPt Op2, LongPoint OffPt ) { LOGGER.entering(DefaultClipper.class.getName(), "addJoin"); final Join j = new Join(); j.outPt1 = Op1; j.outPt2 = Op2; j.setOffPt( OffPt ); joins.add( j ); } //------------------------------------------------------------------------------ private void addLocalMaxPoly( Edge e1, Edge e2, LongPoint pt ) { addOutPt(e1, pt); if (e2.windDelta == 0) { addOutPt( e2, pt ); } if (e1.outIdx == e2.outIdx) { e1.outIdx = Edge.UNASSIGNED; e2.outIdx = Edge.UNASSIGNED; } else if (e1.outIdx < e2.outIdx) { appendPolygon( e1, e2 ); } else { appendPolygon( e2, e1 ); } } //------------------------------------------------------------------------------ private Path.OutPt addLocalMinPoly( Edge e1, Edge e2, LongPoint pt ) { LOGGER.entering( DefaultClipper.class.getName(), "addLocalMinPoly" ); Path.OutPt result; Edge e, prevE; if (e2.isHorizontal() || e1.deltaX > e2.deltaX) { result = addOutPt( e1, pt ); e2.outIdx = e1.outIdx; e1.side = Edge.Side.LEFT; e2.side = Edge.Side.RIGHT; e = e1; if (e.prevInAEL == e2) { prevE = e2.prevInAEL; } else { prevE = e.prevInAEL; } } else { result = addOutPt( e2, pt ); e1.outIdx = e2.outIdx; e1.side = Edge.Side.RIGHT; e2.side = Edge.Side.LEFT; e = e2; if (e.prevInAEL == e1) { prevE = e1.prevInAEL; } else { prevE = e.prevInAEL; } } if (prevE != null && prevE.outIdx >= 0 && Edge.topX( prevE, pt.getY() ) == Edge.topX( e, pt.getY() ) && Edge.slopesEqual( e, prevE, useFullRange ) && e.windDelta != 0 && prevE.windDelta != 0) { final Path.OutPt outPt = addOutPt( prevE, pt ); addJoin( result, outPt, e.getTop() ); } return result; } private Path.OutPt addOutPt( Edge e, LongPoint pt ) { LOGGER.entering( DefaultClipper.class.getName(), "addOutPt" ); if (e.outIdx < 0) { OutRec outRec = createOutRec(); outRec.isOpen = (e.windDelta == 0); Path.OutPt newOp = new Path.OutPt(); outRec.pts = newOp; newOp.idx = outRec.Idx; newOp.pt = pt; newOp.next = newOp; newOp.prev = newOp; if (!outRec.isOpen) setHoleState(e, outRec); e.outIdx = outRec.Idx; //nb: do this after SetZ ! return newOp; } else { final OutRec outRec = polyOuts.get( e.outIdx ); //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' final Path.OutPt op = outRec.getPoints(); boolean ToFront = (e.side == Edge.Side.LEFT); LOGGER.finest( "op=" + op.getPointCount() ); LOGGER.finest( ToFront + " " + pt + " " + op.getPt() ); if (ToFront && pt.equals( op.getPt() )) { return op; } else if (!ToFront && pt.equals( op.prev.getPt() )) { return op.prev; } final Path.OutPt newOp = new Path.OutPt(); newOp.idx = outRec.Idx; newOp.setPt( new LongPoint( pt ) ); newOp.next = op; newOp.prev = op.prev; newOp.prev.next = newOp; op.prev = newOp; if (ToFront) { outRec.setPoints( newOp ); } return newOp; } } private Path.OutPt GetLastOutPt(Edge e) { OutRec outRec = polyOuts.get(e.outIdx); if (e.side == Edge.Side.LEFT) return outRec.pts; else return outRec.pts.prev; } //------------------------------------------------------------------------------ private void appendPolygon( Edge e1, Edge e2 ) { LOGGER.entering( DefaultClipper.class.getName(), "appendPolygon" ); //get the start and ends of both output polygons ... final OutRec outRec1 = polyOuts.get( e1.outIdx ); final OutRec outRec2 = polyOuts.get( e2.outIdx ); LOGGER.finest( "" + e1.outIdx ); LOGGER.finest( "" + e2.outIdx ); OutRec holeStateRec; if (isParam1RightOfParam2( outRec1, outRec2 )) { holeStateRec = outRec2; } else if (isParam1RightOfParam2( outRec2, outRec1 )) { holeStateRec = outRec1; } else { holeStateRec = Path.OutPt.getLowerMostRec( outRec1, outRec2 ); } final Path.OutPt p1_lft = outRec1.getPoints(); final Path.OutPt p1_rt = p1_lft.prev; final Path.OutPt p2_lft = outRec2.getPoints(); final Path.OutPt p2_rt = p2_lft.prev; LOGGER.finest( "p1_lft.getPointCount() = " + p1_lft.getPointCount() ); LOGGER.finest( "p1_rt.getPointCount() = " + p1_rt.getPointCount() ); LOGGER.finest( "p2_lft.getPointCount() = " + p2_lft.getPointCount() ); LOGGER.finest( "p2_rt.getPointCount() = " + p2_rt.getPointCount() ); Edge.Side side; //join e2 poly onto e1 poly and delete pointers to e2 ... if (e1.side == Edge.Side.LEFT) { if (e2.side == Edge.Side.LEFT) { //z y x a b c p2_lft.reversePolyPtLinks(); p2_lft.next = p1_lft; p1_lft.prev = p2_lft; p1_rt.next = p2_rt; p2_rt.prev = p1_rt; outRec1.setPoints( p2_rt ); } else { //x y z a b c p2_rt.next = p1_lft; p1_lft.prev = p2_rt; p2_lft.prev = p1_rt; p1_rt.next = p2_lft; outRec1.setPoints( p2_lft ); } side = Edge.Side.LEFT; } else { if (e2.side == Edge.Side.RIGHT) { //a b c z y x p2_lft.reversePolyPtLinks(); p1_rt.next = p2_rt; p2_rt.prev = p1_rt; p2_lft.next = p1_lft; p1_lft.prev = p2_lft; } else { //a b c x y z p1_rt.next = p2_lft; p2_lft.prev = p1_rt; p1_lft.prev = p2_rt; p2_rt.next = p1_lft; } side = Edge.Side.RIGHT; } outRec1.bottomPt = null; if (holeStateRec.equals( outRec2 )) { if (outRec2.firstLeft != outRec1) { outRec1.firstLeft = outRec2.firstLeft; } outRec1.isHole = outRec2.isHole; } outRec2.setPoints( null ); outRec2.bottomPt = null; outRec2.firstLeft = outRec1; final int OKIdx = e1.outIdx; final int ObsoleteIdx = e2.outIdx; e1.outIdx = Edge.UNASSIGNED; //nb: safe because we only get here via AddLocalMaxPoly e2.outIdx = Edge.UNASSIGNED; Edge e = activeEdges; while (e != null) { if (e.outIdx == ObsoleteIdx) { e.outIdx = OKIdx; e.side = side; break; } e = e.nextInAEL; } outRec2.Idx = outRec1.Idx; } //------------------------------------------------------------------------------ private void buildIntersectList( long topY ) { if (activeEdges == null) { return; } //prepare for sorting ... Edge e = activeEdges; sortedEdges = e; while (e != null) { e.prevInSEL = e.prevInAEL; e.nextInSEL = e.nextInAEL; e.getCurrent().setX( Edge.topX( e, topY ) ); e = e.nextInAEL; } //bubblesort ... boolean isModified = true; while (isModified && sortedEdges != null) { isModified = false; e = sortedEdges; while (e.nextInSEL != null) { final Edge eNext = e.nextInSEL; final LongPoint[] pt = new LongPoint[1]; if (e.getCurrent().getX() > eNext.getCurrent().getX()) { intersectPoint( e, eNext, pt ); final IntersectNode newNode = new IntersectNode(); newNode.edge1 = e; newNode.Edge2 = eNext; newNode.setPt( pt[0] ); intersectList.add( newNode ); swapPositionsInSEL( e, eNext ); isModified = true; } else { e = eNext; } } if (e.prevInSEL != null) { e.prevInSEL.nextInSEL = null; } else { break; } } sortedEdges = null; } //------------------------------------------------------------------------------ private void buildResult( Paths polyg ) { polyg.clear(); for (int i = 0; i < polyOuts.size(); i++) { final OutRec outRec = polyOuts.get( i ); if (outRec.getPoints() == null) { continue; } Path.OutPt p = outRec.getPoints().prev; final int cnt = p.getPointCount(); LOGGER.finest( "cnt = " + cnt ); if (cnt < 2) { continue; } final Path pg = new Path( cnt ); for (int j = 0; j < cnt; j++) { pg.add( p.getPt() ); p = p.prev; } polyg.add( pg ); } } private void buildResult2( PolyTree polytree ) { polytree.Clear(); //add each output polygon/contour to polytree ... for (int i = 0; i < polyOuts.size(); i++) { final OutRec outRec = polyOuts.get( i ); final int cnt = outRec.getPoints() != null ? outRec.getPoints().getPointCount() : 0; if (outRec.isOpen && cnt < 2 || !outRec.isOpen && cnt < 3) { continue; } outRec.fixHoleLinkage(); final PolyNode pn = new PolyNode(); polytree.getAllPolys().add( pn ); outRec.polyNode = pn; Path.OutPt op = outRec.getPoints().prev; for (int j = 0; j < cnt; j++) { pn.getPolygon().add( op.getPt() ); op = op.prev; } } //fixup PolyNode links etc ... for (int i = 0; i < polyOuts.size(); i++) { final OutRec outRec = polyOuts.get( i ); if (outRec.polyNode == null) { continue; } else if (outRec.isOpen) { outRec.polyNode.setOpen( true ); polytree.addChild( outRec.polyNode ); } else if (outRec.firstLeft != null && outRec.firstLeft.polyNode != null) { outRec.firstLeft.polyNode.addChild( outRec.polyNode ); } else { polytree.addChild( outRec.polyNode ); } } } private void copyAELToSEL() { Edge e = activeEdges; sortedEdges = e; while (e != null) { e.prevInSEL = e.prevInAEL; e.nextInSEL = e.nextInAEL; e = e.nextInAEL; } } private OutRec createOutRec() { final OutRec result = new OutRec(); result.Idx = Edge.UNASSIGNED; result.isHole = false; result.isOpen = false; result.firstLeft = null; result.setPoints( null ); result.bottomPt = null; result.polyNode = null; polyOuts.add( result ); result.Idx = polyOuts.size() - 1; return result; } private void deleteFromAEL( Edge e ) { LOGGER.entering( DefaultClipper.class.getName(), "deleteFromAEL" ); final Edge AelPrev = e.prevInAEL; final Edge AelNext = e.nextInAEL; if (AelPrev == null && AelNext == null && e != activeEdges) { return; //already deleted } if (AelPrev != null) { AelPrev.nextInAEL = AelNext; } else { activeEdges = AelNext; } if (AelNext != null) { AelNext.prevInAEL = AelPrev; } e.nextInAEL = null; e.prevInAEL = null; LOGGER.exiting( DefaultClipper.class.getName(), "deleteFromAEL" ); } private void deleteFromSEL( Edge e ) { LOGGER.entering( DefaultClipper.class.getName(), "deleteFromSEL" ); final Edge SelPrev = e.prevInSEL; final Edge SelNext = e.nextInSEL; if (SelPrev == null && SelNext == null && !e.equals( sortedEdges )) { return; //already deleted } if (SelPrev != null) { SelPrev.nextInSEL = SelNext; } else { sortedEdges = SelNext; } if (SelNext != null) { SelNext.prevInSEL = SelPrev; } e.nextInSEL = null; e.prevInSEL = null; } private boolean doHorzSegmentsOverlap( long seg1a, long seg1b, long seg2a, long seg2b ) { if (seg1a > seg1b) { final long tmp = seg1a; seg1a = seg1b; seg1b = tmp; } if (seg2a > seg2b) { final long tmp = seg2a; seg2a = seg2b; seg2b = tmp; } return (seg1a < seg2b) && (seg2a < seg1b); } private void doMaxima( Edge e ) { final Edge eMaxPair = e.getMaximaPair(); if (eMaxPair == null) { if (e.outIdx >= 0) { addOutPt( e, e.getTop() ); } deleteFromAEL( e ); return; } Edge eNext = e.nextInAEL; while (eNext != null && eNext != eMaxPair) { final LongPoint tmp = new LongPoint( e.getTop() ); intersectEdges( e, eNext, tmp ); e.setTop( tmp ); swapPositionsInAEL( e, eNext ); eNext = e.nextInAEL; } if (e.outIdx == Edge.UNASSIGNED && eMaxPair.outIdx == Edge.UNASSIGNED) { deleteFromAEL( e ); deleteFromAEL( eMaxPair ); } else if (e.outIdx >= 0 && eMaxPair.outIdx >= 0) { if (e.outIdx >= 0) { addLocalMaxPoly( e, eMaxPair, e.getTop() ); } deleteFromAEL( e ); deleteFromAEL( eMaxPair ); } else if (e.windDelta == 0) { if (e.outIdx >= 0) { addOutPt( e, e.getTop() ); e.outIdx = Edge.UNASSIGNED; } deleteFromAEL( e ); if (eMaxPair.outIdx >= 0) { addOutPt( eMaxPair, e.getTop() ); eMaxPair.outIdx = Edge.UNASSIGNED; } deleteFromAEL( eMaxPair ); } else { throw new IllegalStateException( "DoMaxima error" ); } } //------------------------------------------------------------------------------ private void doSimplePolygons() { int i = 0; while (i < polyOuts.size()) { final OutRec outrec = polyOuts.get( i++ ); Path.OutPt op = outrec.getPoints(); if (op == null || outrec.isOpen) { continue; } do //for each Pt in Polygon until duplicate found do ... { Path.OutPt op2 = op.next; while (op2 != outrec.getPoints()) { if (op.getPt().equals( op2.getPt() ) && !op2.next.equals( op ) && !op2.prev.equals( op )) { //split the polygon into two ... final Path.OutPt op3 = op.prev; final Path.OutPt op4 = op2.prev; op.prev = op4; op4.next = op; op2.prev = op3; op3.next = op2; outrec.setPoints( op ); final OutRec outrec2 = createOutRec(); outrec2.setPoints( op2 ); updateOutPtIdxs( outrec2 ); if (poly2ContainsPoly1( outrec2.getPoints(), outrec.getPoints() )) { //OutRec2 is contained by OutRec1 ... outrec2.isHole = !outrec.isHole; outrec2.firstLeft = outrec; if (usingPolyTree) { fixupFirstLefts2( outrec2, outrec ); } } else if (poly2ContainsPoly1( outrec.getPoints(), outrec2.getPoints() )) { //OutRec1 is contained by OutRec2 ... outrec2.isHole = outrec.isHole; outrec.isHole = !outrec2.isHole; outrec2.firstLeft = outrec.firstLeft; outrec.firstLeft = outrec2; if (usingPolyTree) { fixupFirstLefts2( outrec, outrec2 ); } } else { //the 2 polygons are separate ... outrec2.isHole = outrec.isHole; outrec2.firstLeft = outrec.firstLeft; if (usingPolyTree) { fixupFirstLefts1( outrec, outrec2 ); } } op2 = op; //ie get ready for the next iteration } op2 = op2.next; } op = op.next; } while (op != outrec.getPoints()); } } //------------------------------------------------------------------------------ private boolean EdgesAdjacent( IntersectNode inode ) { return inode.edge1.nextInSEL == inode.Edge2 || inode.edge1.prevInSEL == inode.Edge2; } //------------------------------------------------------------------------------ public boolean execute(ClipType clipType, Paths solution, PolyFillType FillType) { return execute(clipType, solution, FillType, FillType); } public boolean execute(ClipType clipType, PolyTree polytree) { return execute(clipType, polytree, PolyFillType.EVEN_ODD); } public boolean execute(ClipType clipType, PolyTree polytree, PolyFillType FillType) { return execute(clipType, polytree, FillType, FillType); } public boolean execute(ClipType clipType, Paths solution) { return execute(clipType, solution, PolyFillType.EVEN_ODD); } public boolean execute( ClipType clipType, Paths solution, PolyFillType subjFillType, PolyFillType clipFillType ) { synchronized (this) { if (hasOpenPaths) { throw new IllegalStateException( "Error: PolyTree struct is needed for open path clipping." ); } solution.clear(); this.subjFillType = subjFillType; this.clipFillType = clipFillType; this.clipType = clipType; usingPolyTree = false; boolean succeeded; try { succeeded = executeInternal(); //build the return polygons ... if (succeeded) { buildResult( solution ); } return succeeded; } finally { polyOuts.clear(); } } } public boolean execute( ClipType clipType, PolyTree polytree, PolyFillType subjFillType, PolyFillType clipFillType ) { synchronized (this) { this.subjFillType = subjFillType; this.clipFillType = clipFillType; this.clipType = clipType; usingPolyTree = true; boolean succeeded; try { succeeded = executeInternal(); //build the return polygons ... if (succeeded) { buildResult2( polytree ); } } finally { polyOuts.clear(); } return succeeded; } } //------------------------------------------------------------------------------ private boolean executeInternal() { try { reset(); if (currentLM == null) return false; long botY = popScanbeam(); do { insertLocalMinimaIntoAEL(botY); processHorizontals(); ghostJoins.clear(); if (scanbeam == null) break; long topY = popScanbeam(); if (!processIntersections(topY)) return false; processEdgesAtTopOfScanbeam(topY); botY = topY; } while (scanbeam != null || currentLM != null); //fix orientations ... for (int i = 0; i < polyOuts.size(); i++) { OutRec outRec = polyOuts.get(i); if (outRec.pts == null || outRec.isOpen) continue; if ((outRec.isHole ^ reverseSolution) == (outRec.area() > 0)) outRec.getPoints().reversePolyPtLinks(); } joinCommonEdges(); for (int i = 0; i < polyOuts.size(); i++) { OutRec outRec = polyOuts.get(i); if (outRec.getPoints() == null) continue; else if (outRec.isOpen) fixupOutPolyline(outRec); else fixupOutPolygon(outRec); } if (strictlySimple) doSimplePolygons(); return true; } //catch { return false; } finally { joins.clear(); ghostJoins.clear(); } } //------------------------------------------------------------------------------ private void fixupFirstLefts1( OutRec OldOutRec, OutRec NewOutRec ) { for (int i = 0; i < polyOuts.size(); i++) { final OutRec outRec = polyOuts.get( i ); if (outRec.getPoints() == null || outRec.firstLeft == null) { continue; } final OutRec firstLeft = parseFirstLeft(outRec.firstLeft); if (firstLeft.equals( OldOutRec )) { if (poly2ContainsPoly1( outRec.getPoints(), NewOutRec.getPoints() )) { outRec.firstLeft = NewOutRec; } } } } private void fixupFirstLefts2( OutRec OldOutRec, OutRec NewOutRec ) { for (final OutRec outRec : polyOuts) { if (outRec.firstLeft == OldOutRec ) { outRec.firstLeft = NewOutRec; } } } private boolean fixupIntersectionOrder() { //pre-condition: intersections are sorted bottom-most first. //Now it's crucial that intersections are made only between adjacent edges, //so to ensure this the order of intersections may need adjusting ... Collections.sort( intersectList, intersectNodeComparer ); copyAELToSEL(); final int cnt = intersectList.size(); for (int i = 0; i < cnt; i++) { if (!EdgesAdjacent( intersectList.get( i ) )) { int j = i + 1; while (j < cnt && !EdgesAdjacent( intersectList.get( j ) )) { j++; } if (j == cnt) { return false; } final IntersectNode tmp = intersectList.get( i ); intersectList.set( i, intersectList.get( j ) ); intersectList.set( j, tmp ); } swapPositionsInSEL( intersectList.get( i ).edge1, intersectList.get( i ).Edge2 ); } return true; } //---------------------------------------------------------------------- private void fixupOutPolyline(OutRec outrec) { Path.OutPt pp = outrec.pts; Path.OutPt lastPP = pp.prev; while (pp != lastPP) { pp = pp.next; if (pp.pt.equals(pp.prev.pt)) { if (pp == lastPP) lastPP = pp.prev; Path.OutPt tmpPP = pp.prev; tmpPP.next = pp.next; pp.next.prev = tmpPP; pp = tmpPP; } } if (pp == pp.prev) outrec.pts = null; } private void fixupOutPolygon( OutRec outRec ) { //FixupOutPolygon() - removes duplicate points and simplifies consecutive //parallel edges by removing the middle vertex. Path.OutPt lastOK = null; outRec.bottomPt = null; Path.OutPt pp = outRec.getPoints(); boolean preserveCol = preserveCollinear || strictlySimple; for (;;) { if (pp.prev == pp || pp.prev == pp.next) { outRec.setPoints( null ); return; } //test for duplicate points and collinear edges ... if (pp.getPt().equals( pp.next.getPt() ) || pp.getPt().equals( pp.prev.getPt() ) || Point.slopesEqual( pp.prev.getPt(), pp.getPt(), pp.next.getPt(), useFullRange ) && (!preserveCol || !Point.isPt2BetweenPt1AndPt3( pp.prev.getPt(), pp.getPt(), pp.next.getPt() ))) { lastOK = null; pp.prev.next = pp.next; pp.next.prev = pp.prev; pp = pp.prev; } else if (pp == lastOK) { break; } else { if (lastOK == null) { lastOK = pp; } pp = pp.next; } } outRec.setPoints( pp ); } private OutRec getOutRec( int idx ) { OutRec outrec = polyOuts.get( idx ); while (outrec != polyOuts.get( outrec.Idx )) { outrec = polyOuts.get( outrec.Idx ); } return outrec; } private void insertEdgeIntoAEL( Edge edge, Edge startEdge ) { LOGGER.entering( DefaultClipper.class.getName(), "insertEdgeIntoAEL" ); if (activeEdges == null) { edge.prevInAEL = null; edge.nextInAEL = null; LOGGER.finest( "Edge " + edge.outIdx + " -> " + null ); activeEdges = edge; } else if (startEdge == null && Edge.doesE2InsertBeforeE1( activeEdges, edge )) { edge.prevInAEL = null; edge.nextInAEL = activeEdges; LOGGER.finest( "Edge " + edge.outIdx + " -> " + edge.nextInAEL.outIdx ); activeEdges.prevInAEL = edge; activeEdges = edge; } else { LOGGER.finest( "activeEdges unchanged" ); if (startEdge == null) { startEdge = activeEdges; } while (startEdge.nextInAEL != null && !Edge.doesE2InsertBeforeE1( startEdge.nextInAEL, edge )) { startEdge = startEdge.nextInAEL; } edge.nextInAEL = startEdge.nextInAEL; if (startEdge.nextInAEL != null) { startEdge.nextInAEL.prevInAEL = edge; } edge.prevInAEL = startEdge; startEdge.nextInAEL = edge; } } //------------------------------------------------------------------------------ private void insertLocalMinimaIntoAEL( long botY ) { LOGGER.entering( DefaultClipper.class.getName(), "insertLocalMinimaIntoAEL" ); while (currentLM != null && currentLM.y == botY) { final Edge lb = currentLM.leftBound; final Edge rb = currentLM.rightBound; popLocalMinima(); Path.OutPt Op1 = null; if (lb == null) { insertEdgeIntoAEL( rb, null ); updateWindingCount( rb ); if (rb.isContributing( clipFillType, subjFillType, clipType )) { Op1 = addOutPt( rb, rb.getBot() ); } } else if (rb == null) { insertEdgeIntoAEL( lb, null ); updateWindingCount( lb ); if (lb.isContributing( clipFillType, subjFillType, clipType )) { Op1 = addOutPt( lb, lb.getBot() ); } insertScanbeam( lb.getTop().getY() ); } else { insertEdgeIntoAEL( lb, null ); insertEdgeIntoAEL( rb, lb ); updateWindingCount( lb ); rb.windCnt = lb.windCnt; rb.windCnt2 = lb.windCnt2; if (lb.isContributing( clipFillType, subjFillType, clipType )) { Op1 = addLocalMinPoly( lb, rb, lb.getBot() ); } insertScanbeam( lb.getTop().getY() ); } if (rb != null) { if (rb.isHorizontal()) { addEdgeToSEL( rb ); } else { insertScanbeam( rb.getTop().getY() ); } } if (lb == null || rb == null) { continue; } //if output polygons share an Edge with a horizontal rb, they'll need joining later ... if (Op1 != null && rb.isHorizontal() && ghostJoins.size() > 0 && rb.windDelta != 0) { for (int i = 0; i < ghostJoins.size(); i++) { //if the horizontal Rb and a 'ghost' horizontal overlap, then convert //the 'ghost' join to a real join ready for later ... final Join j = ghostJoins.get( i ); if (doHorzSegmentsOverlap( j.outPt1.getPt().getX(), j.getOffPt().getX(), rb.getBot().getX(), rb.getTop().getX() )) { addJoin( j.outPt1, Op1, j.getOffPt() ); } } } if (lb.outIdx >= 0 && lb.prevInAEL != null && lb.prevInAEL.getCurrent().getX() == lb.getBot().getX() && lb.prevInAEL.outIdx >= 0 && Edge.slopesEqual( lb.prevInAEL, lb, useFullRange ) && lb.windDelta != 0 && lb.prevInAEL.windDelta != 0) { final Path.OutPt Op2 = addOutPt( lb.prevInAEL, lb.getBot() ); addJoin( Op1, Op2, lb.getTop() ); } if (lb.nextInAEL != rb) { if (rb.outIdx >= 0 && rb.prevInAEL.outIdx >= 0 && Edge.slopesEqual( rb.prevInAEL, rb, useFullRange ) && rb.windDelta != 0 && rb.prevInAEL.windDelta != 0) { final Path.OutPt Op2 = addOutPt( rb.prevInAEL, rb.getBot() ); addJoin( Op1, Op2, rb.getTop() ); } Edge e = lb.nextInAEL; if (e != null) { while (e != rb) { //nb: For calculating winding counts etc, IntersectEdges() assumes //that param1 will be to the right of param2 ABOVE the intersection ... intersectEdges( rb, e, lb.getCurrent() ); //order important here e = e.nextInAEL; } } } } } //------------------------------------------------------------------------------ // private void insertScanbeam( long y ) { // LOGGER.entering( DefaultClipper.class.getName(), "insertScanbeam" ); // // if (scanbeam == null) { // scanbeam = new Scanbeam(); // scanbeam.next = null; // scanbeam.y = y; // } // else if (y > scanbeam.y) { // final Scanbeam newSb = new Scanbeam(); // newSb.y = y; // newSb.next = scanbeam; // scanbeam = newSb; // } // else { // Scanbeam sb2 = scanbeam; // while (sb2.next != null && y <= sb2.next.y) { // sb2 = sb2.next; // } // if (y == sb2.y) { // return; //ie ignores duplicates // } // final Scanbeam newSb = new Scanbeam(); // newSb.y = y; // newSb.next = sb2.next; // sb2.next = newSb; // } // } //------------------------------------------------------------------------------ private void intersectEdges( Edge e1, Edge e2, LongPoint pt ) { LOGGER.entering( DefaultClipper.class.getName(), "insersectEdges" ); //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before //e2 in AEL except when e1 is being inserted at the intersection point ... final boolean e1Contributing = e1.outIdx >= 0; final boolean e2Contributing = e2.outIdx >= 0; setZ( pt, e1, e2 ); //if either edge is on an OPEN path ... if (e1.windDelta == 0 || e2.windDelta == 0) { //ignore subject-subject open path intersections UNLESS they //are both open paths, AND they are both 'contributing maximas' ... if (e1.windDelta == 0 && e2.windDelta == 0) { return; } else if (e1.polyTyp == e2.polyTyp && e1.windDelta != e2.windDelta && clipType == ClipType.UNION) { if (e1.windDelta == 0) { if (e2Contributing) { addOutPt( e1, pt ); if (e1Contributing) { e1.outIdx = Edge.UNASSIGNED; } } } else { if (e1Contributing) { addOutPt( e2, pt ); if (e2Contributing) { e2.outIdx = Edge.UNASSIGNED; } } } } else if (e1.polyTyp != e2.polyTyp) { if (e1.windDelta == 0 && Math.abs( e2.windCnt ) == 1 && (clipType != ClipType.UNION || e2.windCnt2 == 0)) { addOutPt( e1, pt ); if (e1Contributing) { e1.outIdx = Edge.UNASSIGNED; } } else if (e2.windDelta == 0 && Math.abs( e1.windCnt ) == 1 && (clipType != ClipType.UNION || e1.windCnt2 == 0)) { addOutPt( e2, pt ); if (e2Contributing) { e2.outIdx = Edge.UNASSIGNED; } } } return; } //update winding counts... //assumes that e1 will be to the Right of e2 ABOVE the intersection if (e1.polyTyp == e2.polyTyp) { if (e1.isEvenOddFillType( clipFillType, subjFillType )) { final int oldE1WindCnt = e1.windCnt; e1.windCnt = e2.windCnt; e2.windCnt = oldE1WindCnt; } else { if (e1.windCnt + e2.windDelta == 0) { e1.windCnt = -e1.windCnt; } else { e1.windCnt += e2.windDelta; } if (e2.windCnt - e1.windDelta == 0) { e2.windCnt = -e2.windCnt; } else { e2.windCnt -= e1.windDelta; } } } else { if (!e2.isEvenOddFillType( clipFillType, subjFillType )) { e1.windCnt2 += e2.windDelta; } else { e1.windCnt2 = e1.windCnt2 == 0 ? 1 : 0; } if (!e1.isEvenOddFillType( clipFillType, subjFillType )) { e2.windCnt2 -= e1.windDelta; } else { e2.windCnt2 = e2.windCnt2 == 0 ? 1 : 0; } } PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; if (e1.polyTyp == PolyType.SUBJECT) { e1FillType = subjFillType; e1FillType2 = clipFillType; } else { e1FillType = clipFillType; e1FillType2 = subjFillType; } if (e2.polyTyp == PolyType.SUBJECT) { e2FillType = subjFillType; e2FillType2 = clipFillType; } else { e2FillType = clipFillType; e2FillType2 = subjFillType; } int e1Wc, e2Wc; switch (e1FillType) { case POSITIVE: e1Wc = e1.windCnt; break; case NEGATIVE: e1Wc = -e1.windCnt; break; default: e1Wc = Math.abs( e1.windCnt ); break; } switch (e2FillType) { case POSITIVE: e2Wc = e2.windCnt; break; case NEGATIVE: e2Wc = -e2.windCnt; break; default: e2Wc = Math.abs( e2.windCnt ); break; } if (e1Contributing && e2Contributing) { if (e1Wc != 0 && e1Wc != 1 || e2Wc != 0 && e2Wc != 1 || e1.polyTyp != e2.polyTyp && clipType != ClipType.XOR) { addLocalMaxPoly( e1, e2, pt ); } else { addOutPt( e1, pt ); addOutPt( e2, pt ); Edge.swapSides( e1, e2 ); Edge.swapPolyIndexes( e1, e2 ); } } else if (e1Contributing) { if (e2Wc == 0 || e2Wc == 1) { addOutPt( e1, pt ); Edge.swapSides( e1, e2 ); Edge.swapPolyIndexes( e1, e2 ); } } else if (e2Contributing) { if (e1Wc == 0 || e1Wc == 1) { addOutPt( e2, pt ); Edge.swapSides( e1, e2 ); Edge.swapPolyIndexes( e1, e2 ); } } else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) { //neither edge is currently contributing ... int e1Wc2, e2Wc2; switch (e1FillType2) { case POSITIVE: e1Wc2 = e1.windCnt2; break; case NEGATIVE: e1Wc2 = -e1.windCnt2; break; default: e1Wc2 = Math.abs( e1.windCnt2 ); break; } switch (e2FillType2) { case POSITIVE: e2Wc2 = e2.windCnt2; break; case NEGATIVE: e2Wc2 = -e2.windCnt2; break; default: e2Wc2 = Math.abs( e2.windCnt2 ); break; } if (e1.polyTyp != e2.polyTyp) { addLocalMinPoly( e1, e2, pt ); } else if (e1Wc == 1 && e2Wc == 1) { switch (clipType) { case INTERSECTION: if (e1Wc2 > 0 && e2Wc2 > 0) { addLocalMinPoly( e1, e2, pt ); } break; case UNION: if (e1Wc2 <= 0 && e2Wc2 <= 0) { addLocalMinPoly( e1, e2, pt ); } break; case DIFFERENCE: if (e1.polyTyp == PolyType.CLIP && e1Wc2 > 0 && e2Wc2 > 0 || e1.polyTyp == PolyType.SUBJECT && e1Wc2 <= 0 && e2Wc2 <= 0) { addLocalMinPoly( e1, e2, pt ); } break; case XOR: addLocalMinPoly( e1, e2, pt ); break; } } else { Edge.swapSides( e1, e2 ); } } } private void intersectPoint( Edge edge1, Edge edge2, LongPoint[] ipV ) { final LongPoint ip = ipV[0] = new LongPoint(); double b1, b2; //nb: with very large coordinate values, it's possible for SlopesEqual() to //return false but for the edge.Dx value be equal due to double precision rounding. if (edge1.deltaX == edge2.deltaX) { ip.setY( edge1.getCurrent().getY() ); ip.setX( Edge.topX( edge1, ip.getY() ) ); return; } if (edge1.getDelta().getX() == 0) { ip.setX( edge1.getBot().getX() ); if (edge2.isHorizontal()) { ip.setY( edge2.getBot().getY() ); } else { b2 = edge2.getBot().getY() - edge2.getBot().getX() / edge2.deltaX; ip.setY( Math.round( ip.getX() / edge2.deltaX + b2 ) ); } } else if (edge2.getDelta().getX() == 0) { ip.setX( edge2.getBot().getX() ); if (edge1.isHorizontal()) { ip.setY( edge1.getBot().getY() ); } else { b1 = edge1.getBot().getY() - edge1.getBot().getX() / edge1.deltaX; ip.setY( Math.round( ip.getX() / edge1.deltaX + b1 ) ); } } else { b1 = edge1.getBot().getX() - edge1.getBot().getY() * edge1.deltaX; b2 = edge2.getBot().getX() - edge2.getBot().getY() * edge2.deltaX; final double q = (b2 - b1) / (edge1.deltaX - edge2.deltaX); ip.setY( Math.round( q ) ); if (Math.abs( edge1.deltaX ) < Math.abs( edge2.deltaX )) { ip.setX( Math.round( edge1.deltaX * q + b1 ) ); } else { ip.setX( Math.round( edge2.deltaX * q + b2 ) ); } } if (ip.getY() < edge1.getTop().getY() || ip.getY() < edge2.getTop().getY()) { if (edge1.getTop().getY() > edge2.getTop().getY()) { ip.setY( edge1.getTop().getY() ); } else { ip.setY( edge2.getTop().getY() ); } if (Math.abs( edge1.deltaX ) < Math.abs( edge2.deltaX )) { ip.setX( Edge.topX( edge1, ip.getY() ) ); } else { ip.setX( Edge.topX( edge2, ip.getY() ) ); } } //finally, don't allow 'ip' to be BELOW curr.getY() (ie bottom of scanbeam) ... if (ip.getY() > edge1.getCurrent().getY()) { ip.setY( edge1.getCurrent().getY() ); //better to use the more vertical edge to derive X ... if (Math.abs( edge1.deltaX ) > Math.abs( edge2.deltaX )) { ip.setX( Edge.topX( edge2, ip.getY() ) ); } else { ip.setX( Edge.topX( edge1, ip.getY() ) ); } } } private void joinCommonEdges() { for (int i = 0; i < joins.size(); i++) { final Join join = joins.get( i ); final OutRec outRec1 = getOutRec( join.outPt1.idx ); OutRec outRec2 = getOutRec( join.outPt2.idx ); if (outRec1.getPoints() == null || outRec2.getPoints() == null) { continue; } if (outRec1.isOpen || outRec2.isOpen) continue; //get the polygon fragment with the correct hole state (FirstLeft) //before calling JoinPoints() ... OutRec holeStateRec; if (outRec1 == outRec2) { holeStateRec = outRec1; } else if (isParam1RightOfParam2( outRec1, outRec2 )) { holeStateRec = outRec2; } else if (isParam1RightOfParam2( outRec2, outRec1 )) { holeStateRec = outRec1; } else { holeStateRec = Path.OutPt.getLowerMostRec( outRec1, outRec2 ); } if (!joinPoints( join, outRec1, outRec2 )) { continue; } if (outRec1 == outRec2) { //instead of joining two polygons, we've just created a new one by //splitting one polygon into two. outRec1.setPoints( join.outPt1 ); outRec1.bottomPt = null; outRec2 = createOutRec(); outRec2.setPoints( join.outPt2 ); //update all OutRec2.Pts Idx's ... updateOutPtIdxs( outRec2 ); //We now need to check every OutRec.FirstLeft pointer. If it points //to OutRec1 it may need to point to OutRec2 instead ... if (usingPolyTree) { for (int j = 0; j < polyOuts.size() - 1; j++) { final OutRec oRec = polyOuts.get( j ); if (oRec.getPoints() == null || parseFirstLeft(oRec.firstLeft) != outRec1 || oRec.isHole == outRec1.isHole) { continue; } if (poly2ContainsPoly1( oRec.getPoints(), join.outPt2 )) { oRec.firstLeft = outRec2; } } } if (poly2ContainsPoly1( outRec2.getPoints(), outRec1.getPoints() )) { //outRec2 is contained by outRec1 ... outRec2.isHole = !outRec1.isHole; outRec2.firstLeft = outRec1; //fixup FirstLeft pointers that may need reassigning to OutRec1 if (usingPolyTree) { fixupFirstLefts2( outRec2, outRec1 ); } if ((outRec2.isHole ^ reverseSolution) == outRec2.area() > 0) { outRec2.getPoints().reversePolyPtLinks(); } } else if (poly2ContainsPoly1( outRec1.getPoints(), outRec2.getPoints() )) { //outRec1 is contained by outRec2 ... outRec2.isHole = outRec1.isHole; outRec1.isHole = !outRec2.isHole; outRec2.firstLeft = outRec1.firstLeft; outRec1.firstLeft = outRec2; //fixup FirstLeft pointers that may need reassigning to OutRec1 if (usingPolyTree) { fixupFirstLefts2( outRec1, outRec2 ); } if ((outRec1.isHole ^ reverseSolution) == outRec1.area() > 0) { outRec1.getPoints().reversePolyPtLinks(); } } else { //the 2 polygons are completely separate ... outRec2.isHole = outRec1.isHole; outRec2.firstLeft = outRec1.firstLeft; //fixup FirstLeft pointers that may need reassigning to OutRec2 if (usingPolyTree) { fixupFirstLefts1( outRec1, outRec2 ); } } } else { //joined 2 polygons together ... outRec2.setPoints( null ); outRec2.bottomPt = null; outRec2.Idx = outRec1.Idx; outRec1.isHole = holeStateRec.isHole; if (holeStateRec == outRec2) { outRec1.firstLeft = outRec2.firstLeft; } outRec2.firstLeft = outRec1; //fixup FirstLeft pointers that may need reassigning to OutRec1 if (usingPolyTree) { fixupFirstLefts2( outRec2, outRec1 ); } } } } private long popScanbeam() { LOGGER.entering( DefaultClipper.class.getName(), "popBeam" ); final long y = scanbeam.y; scanbeam = scanbeam.next; return y; } private void processEdgesAtTopOfScanbeam( long topY ) { LOGGER.entering( DefaultClipper.class.getName(), "processEdgesAtTopOfScanbeam" ); Edge e = activeEdges; while (e != null) { //1. process maxima, treating them as if they're 'bent' horizontal edges, // but exclude maxima with horizontal edges. nb: e can't be a horizontal. boolean IsMaximaEdge = e.isMaxima( topY ); if (IsMaximaEdge) { final Edge eMaxPair = e.getMaximaPair(); IsMaximaEdge = eMaxPair == null || !eMaxPair.isHorizontal(); } if (IsMaximaEdge) { if (strictlySimple) InsertMaxima(e.getTop().getX()); final Edge ePrev = e.prevInAEL; doMaxima( e ); if (ePrev == null) { e = activeEdges; } else { e = ePrev.nextInAEL; } } else { //2. promote horizontal edges, otherwise update Curr.getX() and Curr.getY() ... if (e.isIntermediate( topY ) && e.nextInLML.isHorizontal()) { final Edge[] t = new Edge[] { e }; updateEdgeIntoAEL( t ); e = t[0]; if (e.outIdx >= 0) { addOutPt( e, e.getBot() ); } addEdgeToSEL( e ); } else { e.getCurrent().setX( Edge.topX( e, topY ) ); e.getCurrent().setY( topY ); } //When StrictlySimple and 'e' is being touched by another edge, then //make sure both edges have a vertex here ... if (strictlySimple) { final Edge ePrev = e.prevInAEL; if (e.outIdx >= 0 && e.windDelta != 0 && ePrev != null && ePrev.outIdx >= 0 && ePrev.getCurrent().getX() == e.getCurrent().getX() && ePrev.windDelta != 0) { final LongPoint ip = new LongPoint( e.getCurrent() ); setZ( ip, ePrev, e ); final Path.OutPt op = addOutPt( ePrev, ip ); final Path.OutPt op2 = addOutPt( e, ip ); addJoin( op, op2, ip ); //StrictlySimple (type-3) join } } e = e.nextInAEL; } } //3. Process horizontals at the Top of the scanbeam ... processHorizontals(); maxima = null; //4. Promote intermediate vertices ... e = activeEdges; while (e != null) { if (e.isIntermediate( topY )) { Path.OutPt op = null; if (e.outIdx >= 0) { op = addOutPt( e, e.getTop() ); } final Edge[] t = new Edge[] { e }; updateEdgeIntoAEL( t ); e = t[0]; //if output polygons share an edge, they'll need joining later ... final Edge ePrev = e.prevInAEL; final Edge eNext = e.nextInAEL; if (ePrev != null && ePrev.getCurrent().getX() == e.getBot().getX() && ePrev.getCurrent().getY() == e.getBot().getY() && op != null && ePrev.outIdx >= 0 && ePrev.getCurrent().getY() > ePrev.getTop().getY() && Edge.slopesEqual( e, ePrev, useFullRange ) && e.windDelta != 0 && ePrev.windDelta != 0) { final Path.OutPt op2 = addOutPt( ePrev, e.getBot() ); addJoin( op, op2, e.getTop() ); } else if (eNext != null && eNext.getCurrent().getX() == e.getBot().getX() && eNext.getCurrent().getY() == e.getBot().getY() && op != null && eNext.outIdx >= 0 && eNext.getCurrent().getY() > eNext.getTop().getY() && Edge.slopesEqual( e, eNext, useFullRange ) && e.windDelta != 0 && eNext.windDelta != 0) { final Path.OutPt op2 = addOutPt( eNext, e.getBot() ); addJoin( op, op2, e.getTop() ); } } e = e.nextInAEL; } LOGGER.exiting( DefaultClipper.class.getName(), "processEdgesAtTopOfScanbeam" ); } private void processHorizontal( Edge horzEdge ) { LOGGER.entering( DefaultClipper.class.getName(), "isHorizontal" ); final Direction[] dir = new Direction[1]; final long[] horzLeft = new long[1], horzRight = new long[1]; boolean IsOpen = horzEdge.outIdx >= 0 && polyOuts.get(horzEdge.outIdx).isOpen; getHorzDirection( horzEdge, dir, horzLeft, horzRight ); Edge eLastHorz = horzEdge, eMaxPair = null; while (eLastHorz.nextInLML != null && eLastHorz.nextInLML.isHorizontal()) { eLastHorz = eLastHorz.nextInLML; } if (eLastHorz.nextInLML == null) { eMaxPair = eLastHorz.getMaximaPair(); } Path.Maxima currMax = maxima; if (currMax != null) { //get the first maxima in range (X) ... if (dir[0] == Direction.LEFT_TO_RIGHT) { while (currMax != null && currMax.X <= horzEdge.getBot().getX()) currMax = currMax.Next; if (currMax != null && currMax.X >= eLastHorz.getBot().getX()) currMax = null; } else { while (currMax.Next != null && currMax.Next.X < horzEdge.getBot().getX()) currMax = currMax.Next; if (currMax.X <= eLastHorz.getTop().getX()) currMax = null; } } Path.OutPt op1 = null; for (;;) { //loop through consec. horizontal edges final boolean IsLastHorz = horzEdge == eLastHorz; Edge e = horzEdge.getNextInAEL( dir[0] ); while (e != null) { //this code block inserts extra coords into horizontal edges (in output //polygons) whereever maxima touch these horizontal edges. This helps //'simplifying' polygons (ie if the Simplify property is set). if (currMax != null) { if (dir[0] == Direction.LEFT_TO_RIGHT) { while (currMax != null && currMax.X < e.getCurrent().getX()) { if (horzEdge.outIdx >= 0 && !IsOpen) addOutPt(horzEdge, new LongPoint(currMax.X, horzEdge.getBot().getY())); currMax = currMax.Next; } } else { while (currMax != null && currMax.X > e.getCurrent().getX()) { if (horzEdge.outIdx >= 0 && !IsOpen) addOutPt(horzEdge, new LongPoint(currMax.X, horzEdge.getBot().getY())); currMax = currMax.Prev; } } }; if (dir[0] == Direction.LEFT_TO_RIGHT && e.getCurrent().getX() > horzRight[0] || dir[0] == Direction.RIGHT_TO_LEFT && e.getCurrent().getX() < horzLeft[0]) break; //Also break if we've got to the end of an intermediate horizontal edge ... //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. if (e.getCurrent().getX() == horzEdge.getTop().getX() && horzEdge.nextInLML != null && e.deltaX < horzEdge.nextInLML.deltaX) break; if (horzEdge.outIdx >= 0 && !IsOpen) //note: may be done multiple times { op1 = addOutPt(horzEdge, e.getCurrent()); Edge eNextHorz = sortedEdges; while (eNextHorz != null) { if (eNextHorz.outIdx >= 0 && doHorzSegmentsOverlap(horzEdge.getBot().getX(), horzEdge.getTop().getX(), eNextHorz.getBot().getX(), eNextHorz.getTop().getX())) { Path.OutPt op2 = GetLastOutPt(eNextHorz); addJoin(op2, op1, eNextHorz.getTop()); } eNextHorz = eNextHorz.nextInSEL; } addGhostJoin(op1, horzEdge.getBot()); } //OK, so far we're still in range of the horizontal Edge but make sure //we're at the last of consec. horizontals when matching with eMaxPair if(e == eMaxPair && IsLastHorz) { if (horzEdge.outIdx >= 0) addLocalMaxPoly(horzEdge, eMaxPair, horzEdge.getTop()); deleteFromAEL(horzEdge); deleteFromAEL(eMaxPair); return; } if(dir[0] == Direction.LEFT_TO_RIGHT) { LongPoint Pt = new LongPoint(e.getCurrent().getX(), horzEdge.getCurrent().getY()); intersectEdges(horzEdge, e, Pt); } else { LongPoint Pt = new LongPoint(e.getCurrent().getX(), horzEdge.getCurrent().getY()); intersectEdges(e, horzEdge, Pt); } Edge eNext = e.getNextInAEL(dir[0]); swapPositionsInAEL(horzEdge, e); e = eNext; } //end while //Break out of loop if HorzEdge.NextInLML is not also horizontal ... if (horzEdge.nextInLML == null || !horzEdge.nextInLML.isHorizontal()) break; Edge[] temp = new Edge[1]; temp[0] = horzEdge; updateEdgeIntoAEL(temp); horzEdge = temp[0]; if (horzEdge.outIdx >= 0) addOutPt(horzEdge, horzEdge.getBot()); getHorzDirection(horzEdge, dir, horzLeft, horzRight); } //end for (;;) if (horzEdge.outIdx >= 0 && op1 == null) { op1 = GetLastOutPt(horzEdge); Edge eNextHorz = sortedEdges; while (eNextHorz != null) { if (eNextHorz.outIdx >= 0 && doHorzSegmentsOverlap(horzEdge.getBot().getX(), horzEdge.getTop().getX(), eNextHorz.getBot().getX(), eNextHorz.getTop().getX())) { Path.OutPt op2 = GetLastOutPt(eNextHorz); addJoin(op2, op1, eNextHorz.getTop()); } eNextHorz = eNextHorz.nextInSEL; } addGhostJoin(op1, horzEdge.getTop()); } if (horzEdge.nextInLML != null) { if (horzEdge.outIdx >= 0) { op1 = addOutPt( horzEdge, horzEdge.getTop()); final Edge[] t = new Edge[] { horzEdge }; updateEdgeIntoAEL( t ); horzEdge = t[0]; if (horzEdge.windDelta == 0) { return; } //nb: HorzEdge is no longer horizontal here final Edge ePrev = horzEdge.prevInAEL; final Edge eNext = horzEdge.nextInAEL; if (ePrev != null && ePrev.getCurrent().getX() == horzEdge.getBot().getX() && ePrev.getCurrent().getY() == horzEdge.getBot().getY() && ePrev.windDelta != 0 && ePrev.outIdx >= 0 && ePrev.getCurrent().getY() > ePrev.getTop().getY() && Edge.slopesEqual( horzEdge, ePrev, useFullRange )) { final Path.OutPt op2 = addOutPt( ePrev, horzEdge.getBot() ); addJoin( op1, op2, horzEdge.getTop() ); } else if (eNext != null && eNext.getCurrent().getX() == horzEdge.getBot().getX() && eNext.getCurrent().getY() == horzEdge.getBot().getY() && eNext.windDelta != 0 && eNext.outIdx >= 0 && eNext.getCurrent().getY() > eNext.getTop().getY() && Edge.slopesEqual( horzEdge, eNext, useFullRange )) { final Path.OutPt op2 = addOutPt( eNext, horzEdge.getBot() ); addJoin( op1, op2, horzEdge.getTop() ); } } else { final Edge[] t = new Edge[] { horzEdge }; updateEdgeIntoAEL( t ); horzEdge = t[0]; } } else { if (horzEdge.outIdx >= 0) { addOutPt( horzEdge, horzEdge.getTop() ); } deleteFromAEL( horzEdge ); } } //------------------------------------------------------------------------------ private void processHorizontals( ) { LOGGER.entering( DefaultClipper.class.getName(), "processHorizontals" ); Edge horzEdge = sortedEdges; while (horzEdge != null) { deleteFromSEL( horzEdge ); processHorizontal( horzEdge); horzEdge = sortedEdges; } } //------------------------------------------------------------------------------ private boolean processIntersections( long topY ) { LOGGER.entering( DefaultClipper.class.getName(), "processIntersections" ); if (activeEdges == null) { return true; } try { buildIntersectList( topY ); if (intersectList.size() == 0) { return true; } if (intersectList.size() == 1 || fixupIntersectionOrder()) { processIntersectList(); } else { return false; } } catch (final Exception e) { sortedEdges = null; intersectList.clear(); throw new IllegalStateException( "ProcessIntersections error", e ); } sortedEdges = null; return true; } private void processIntersectList() { for (int i = 0; i < intersectList.size(); i++) { final IntersectNode iNode = intersectList.get( i ); { intersectEdges( iNode.edge1, iNode.Edge2, iNode.getPt() ); swapPositionsInAEL( iNode.edge1, iNode.Edge2 ); } } intersectList.clear(); } //------------------------------------------------------------------------------ @Override protected void reset() { super.reset(); scanbeam = null; maxima = null; activeEdges = null; sortedEdges = null; LocalMinima lm = minimaList; while (lm != null) { insertScanbeam( lm.y ); lm = lm.next; } } private void setHoleState( Edge e, OutRec outRec ) { boolean isHole = false; Edge e2 = e.prevInAEL; while (e2 != null) { if (e2.outIdx >= 0 && e2.windDelta != 0) { isHole = !isHole; if (outRec.firstLeft == null) { outRec.firstLeft = polyOuts.get( e2.outIdx ); } } e2 = e2.prevInAEL; } if (isHole) { outRec.isHole = true; } } private void setZ( LongPoint pt, Edge e1, Edge e2 ) { if (pt.getZ() != 0 || zFillFunction == null) { return; } else if (pt.equals( e1.getBot() )) { pt.setZ( e1.getBot().getZ() ); } else if (pt.equals( e1.getTop() )) { pt.setZ( e1.getTop().getZ() ); } else if (pt.equals( e2.getBot() )) { pt.setZ( e2.getBot().getZ() ); } else if (pt.equals( e2.getTop() )) { pt.setZ( e2.getTop().getZ() ); } else { zFillFunction.zFill( e1.getBot(), e1.getTop(), e2.getBot(), e2.getTop(), pt ); } } private void swapPositionsInAEL( Edge edge1, Edge edge2 ) { LOGGER.entering( DefaultClipper.class.getName(), "swapPositionsInAEL" ); //check that one or other edge hasn't already been removed from AEL ... if (edge1.nextInAEL == edge1.prevInAEL || edge2.nextInAEL == edge2.prevInAEL) { return; } if (edge1.nextInAEL == edge2) { final Edge next = edge2.nextInAEL; if (next != null) { next.prevInAEL = edge1; } final Edge prev = edge1.prevInAEL; if (prev != null) { prev.nextInAEL = edge2; } edge2.prevInAEL = prev; edge2.nextInAEL = edge1; edge1.prevInAEL = edge2; edge1.nextInAEL = next; } else if (edge2.nextInAEL == edge1) { final Edge next = edge1.nextInAEL; if (next != null) { next.prevInAEL = edge2; } final Edge prev = edge2.prevInAEL; if (prev != null) { prev.nextInAEL = edge1; } edge1.prevInAEL = prev; edge1.nextInAEL = edge2; edge2.prevInAEL = edge1; edge2.nextInAEL = next; } else { final Edge next = edge1.nextInAEL; final Edge prev = edge1.prevInAEL; edge1.nextInAEL = edge2.nextInAEL; if (edge1.nextInAEL != null) { edge1.nextInAEL.prevInAEL = edge1; } edge1.prevInAEL = edge2.prevInAEL; if (edge1.prevInAEL != null) { edge1.prevInAEL.nextInAEL = edge1; } edge2.nextInAEL = next; if (edge2.nextInAEL != null) { edge2.nextInAEL.prevInAEL = edge2; } edge2.prevInAEL = prev; if (edge2.prevInAEL != null) { edge2.prevInAEL.nextInAEL = edge2; } } if (edge1.prevInAEL == null) { activeEdges = edge1; } else if (edge2.prevInAEL == null) { activeEdges = edge2; } LOGGER.exiting( DefaultClipper.class.getName(), "swapPositionsInAEL" ); } //------------------------------------------------------------------------------; private void swapPositionsInSEL( Edge edge1, Edge edge2 ) { if (edge1.nextInSEL == null && edge1.prevInSEL == null) { return; } if (edge2.nextInSEL == null && edge2.prevInSEL == null) { return; } if (edge1.nextInSEL == edge2) { final Edge next = edge2.nextInSEL; if (next != null) { next.prevInSEL = edge1; } final Edge prev = edge1.prevInSEL; if (prev != null) { prev.nextInSEL = edge2; } edge2.prevInSEL = prev; edge2.nextInSEL = edge1; edge1.prevInSEL = edge2; edge1.nextInSEL = next; } else if (edge2.nextInSEL == edge1) { final Edge next = edge1.nextInSEL; if (next != null) { next.prevInSEL = edge2; } final Edge prev = edge2.prevInSEL; if (prev != null) { prev.nextInSEL = edge1; } edge1.prevInSEL = prev; edge1.nextInSEL = edge2; edge2.prevInSEL = edge1; edge2.nextInSEL = next; } else { final Edge next = edge1.nextInSEL; final Edge prev = edge1.prevInSEL; edge1.nextInSEL = edge2.nextInSEL; if (edge1.nextInSEL != null) { edge1.nextInSEL.prevInSEL = edge1; } edge1.prevInSEL = edge2.prevInSEL; if (edge1.prevInSEL != null) { edge1.prevInSEL.nextInSEL = edge1; } edge2.nextInSEL = next; if (edge2.nextInSEL != null) { edge2.nextInSEL.prevInSEL = edge2; } edge2.prevInSEL = prev; if (edge2.prevInSEL != null) { edge2.prevInSEL.nextInSEL = edge2; } } if (edge1.prevInSEL == null) { sortedEdges = edge1; } else if (edge2.prevInSEL == null) { sortedEdges = edge2; } } private void updateEdgeIntoAEL( Edge[] eV ) { Edge e = eV[0]; if (e.nextInLML == null) { throw new IllegalStateException( "UpdateEdgeIntoAEL: invalid call" ); } final Edge AelPrev = e.prevInAEL; final Edge AelNext = e.nextInAEL; e.nextInLML.outIdx = e.outIdx; if (AelPrev != null) { AelPrev.nextInAEL = e.nextInLML; } else { activeEdges = e.nextInLML; } if (AelNext != null) { AelNext.prevInAEL = e.nextInLML; } e.nextInLML.side = e.side; e.nextInLML.windDelta = e.windDelta; e.nextInLML.windCnt = e.windCnt; e.nextInLML.windCnt2 = e.windCnt2; eV[0] = e = e.nextInLML; e.setCurrent( e.getBot() ); e.prevInAEL = AelPrev; e.nextInAEL = AelNext; if (!e.isHorizontal()) { insertScanbeam( e.getTop().getY() ); } } private void updateOutPtIdxs( OutRec outrec ) { Path.OutPt op = outrec.getPoints(); do { op.idx = outrec.Idx; op = op.prev; } while (op != outrec.getPoints()); } private void updateWindingCount( Edge edge ) { LOGGER.entering( DefaultClipper.class.getName(), "updateWindingCount" ); Edge e = edge.prevInAEL; //find the edge of the same polytype that immediately preceeds 'edge' in AEL while (e != null && (e.polyTyp != edge.polyTyp || e.windDelta == 0)) { e = e.prevInAEL; } if (e == null) { edge.windCnt = edge.windDelta == 0 ? 1 : edge.windDelta; edge.windCnt2 = 0; e = activeEdges; //ie get ready to calc WindCnt2 } else if (edge.windDelta == 0 && clipType != ClipType.UNION) { edge.windCnt = 1; edge.windCnt2 = e.windCnt2; e = e.nextInAEL; //ie get ready to calc WindCnt2 } else if (edge.isEvenOddFillType( clipFillType, subjFillType )) { //EvenOdd filling ... if (edge.windDelta == 0) { //are we inside a subj polygon ... boolean Inside = true; Edge e2 = e.prevInAEL; while (e2 != null) { if (e2.polyTyp == e.polyTyp && e2.windDelta != 0) { Inside = !Inside; } e2 = e2.prevInAEL; } edge.windCnt = Inside ? 0 : 1; } else { edge.windCnt = edge.windDelta; } edge.windCnt2 = e.windCnt2; e = e.nextInAEL; //ie get ready to calc WindCnt2 } else { //nonZero, Positive or Negative filling ... if (e.windCnt * e.windDelta < 0) { //prev edge is 'decreasing' WindCount (WC) toward zero //so we're outside the previous polygon ... if (Math.abs( e.windCnt ) > 1) { //outside prev poly but still inside another. //when reversing direction of prev poly use the same WC if (e.windDelta * edge.windDelta < 0) { edge.windCnt = e.windCnt; } else { edge.windCnt = e.windCnt + edge.windDelta; } } else { //now outside all polys of same polytype so set own WC ... edge.windCnt = edge.windDelta == 0 ? 1 : edge.windDelta; } } else { //prev edge is 'increasing' WindCount (WC) away from zero //so we're inside the previous polygon ... if (edge.windDelta == 0) { edge.windCnt = e.windCnt < 0 ? e.windCnt - 1 : e.windCnt + 1; } else if (e.windDelta * edge.windDelta < 0) { edge.windCnt = e.windCnt; } else { edge.windCnt = e.windCnt + edge.windDelta; } } edge.windCnt2 = e.windCnt2; e = e.nextInAEL; //ie get ready to calc WindCnt2 } //update WindCnt2 ... if (edge.isEvenOddAltFillType( clipFillType, subjFillType )) { //EvenOdd filling ... while (e != edge) { if (e.windDelta != 0) { edge.windCnt2 = edge.windCnt2 == 0 ? 1 : 0; } e = e.nextInAEL; } } else { //nonZero, Positive or Negative filling ... while (e != edge) { edge.windCnt2 += e.windDelta; e = e.nextInAEL; } } } } //end Clipper