
14 changed files with 3 additions and 5063 deletions
@ -1,54 +0,0 @@ |
|||||
package com.visual.open.anpr.core.clipper; |
|
||||
|
|
||||
import com.visual.open.anpr.core.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); |
|
||||
} |
|
@ -1,691 +0,0 @@ |
|||||
package com.visual.open.anpr.core.clipper; |
|
||||
|
|
||||
import com.visual.open.anpr.core.clipper.Path.OutRec; |
|
||||
import com.visual.open.anpr.core.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" ); |
|
||||
} |
|
||||
} |
|
@ -1,481 +0,0 @@ |
|||||
package com.visual.open.anpr.core.clipper; |
|
||||
|
|
||||
import com.visual.open.anpr.core.clipper.Clipper.*; |
|
||||
import com.visual.open.anpr.core.clipper.Point.DoublePoint; |
|
||||
import com.visual.open.anpr.core.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; |
|
||||
} |
|
||||
//------------------------------------------------------------------------------
|
|
||||
} |
|
File diff suppressed because it is too large
@ -1,339 +0,0 @@ |
|||||
package com.visual.open.anpr.core.clipper; |
|
||||
|
|
||||
import com.visual.open.anpr.core.clipper.Clipper.ClipType; |
|
||||
import com.visual.open.anpr.core.clipper.Clipper.Direction; |
|
||||
import com.visual.open.anpr.core.clipper.Clipper.PolyFillType; |
|
||||
import com.visual.open.anpr.core.clipper.Clipper.PolyType; |
|
||||
import com.visual.open.anpr.core.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 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; |
|
||||
|
|
||||
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(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
@ -1,26 +0,0 @@ |
|||||
package com.visual.open.anpr.core.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; |
|
||||
} |
|
||||
} |
|
@ -1,414 +0,0 @@ |
|||||
package com.visual.open.anpr.core.clipper; |
|
||||
|
|
||||
import com.visual.open.anpr.core.clipper.Point.LongPoint; |
|
||||
|
|
||||
import java.util.ArrayList; |
|
||||
import java.util.Collections; |
|
||||
|
|
||||
/** |
|
||||
* A pure convenience class to avoid writing List<IntPoint> everywhere. |
|
||||
* |
|
||||
* @author Tobias Mahlmann |
|
||||
* |
|
||||
*/ |
|
||||
public class Path extends ArrayList<LongPoint> { |
|
||||
static class Join { |
|
||||
OutPt outPt1; |
|
||||
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 OutPt bPt1 = outRec1.bottomPt; |
|
||||
final 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(OutPt btmPt1, OutPt btmPt2 ) { |
|
||||
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 OutPt duplicate(boolean InsertAfter ) { |
|
||||
final OutPt result = new 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; |
|
||||
} |
|
||||
|
|
||||
OutPt getBottomPt() { |
|
||||
OutPt dups = null; |
|
||||
OutPt p = next; |
|
||||
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() { |
|
||||
|
|
||||
OutPt pp1; |
|
||||
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() { |
|
||||
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 OutPt pts; |
|
||||
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 OutPt getPoints() { |
|
||||
return pts; |
|
||||
} |
|
||||
|
|
||||
public static OutRec parseFirstLeft( OutRec firstLeft ) { |
|
||||
while (firstLeft != null && firstLeft.getPoints() == null) { |
|
||||
firstLeft = firstLeft.firstLeft; |
|
||||
} |
|
||||
return firstLeft; |
|
||||
} |
|
||||
|
|
||||
public void setPoints( OutPt pts ) { |
|
||||
this.pts = pts; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static OutPt excludeOp(OutPt op ) { |
|
||||
final 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(); |
|
||||
} |
|
||||
|
|
||||
OutPt[] outPts = new OutPt[cnt]; |
|
||||
for (int i = 0; i < cnt; ++i) { |
|
||||
outPts[i] = new 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; |
|
||||
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; |
|
||||
} |
|
||||
} |
|
@ -1,125 +0,0 @@ |
|||||
package com.visual.open.anpr.core.clipper; |
|
||||
|
|
||||
import java.util.ArrayList; |
|
||||
|
|
||||
/** |
|
||||
* A pure convenience class to avoid writing List<Path> 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(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
@ -1,220 +0,0 @@ |
|||||
package com.visual.open.anpr.core.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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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; |
|
||||
} |
|
||||
|
|
||||
@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
|
|
@ -1,116 +0,0 @@ |
|||||
package com.visual.open.anpr.core.clipper; |
|
||||
|
|
||||
import com.visual.open.anpr.core.clipper.Clipper.EndType; |
|
||||
import com.visual.open.anpr.core.clipper.Clipper.JoinType; |
|
||||
import com.visual.open.anpr.core.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; |
|
||||
|
|
||||
} |
|
||||
|
|
||||
} |
|
@ -1,37 +0,0 @@ |
|||||
package com.visual.open.anpr.core.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; |
|
||||
|
|
||||
} |
|
||||
|
|
||||
} |
|
@ -1,39 +0,0 @@ |
|||||
package com.visual.open.anpr.core.domain; |
|
||||
|
|
||||
import org.opencv.core.Point; |
|
||||
|
|
||||
import java.util.List; |
|
||||
|
|
||||
public class Polygon { |
|
||||
private List<Point> points; |
|
||||
|
|
||||
public Polygon(List<Point> points){ |
|
||||
this.points = points; |
|
||||
} |
|
||||
|
|
||||
public double getArea(){ |
|
||||
int i, j; |
|
||||
double area = 0; |
|
||||
for (i = 0; i < this.points.size(); i++) |
|
||||
{ |
|
||||
j = (i + 1) % this.points.size(); |
|
||||
area += this.points.get(i).x * this.points.get(j).y; |
|
||||
area -= this.points.get(i).y * this.points.get(j).x; |
|
||||
} |
|
||||
area /= 2; |
|
||||
return Math.abs(area); |
|
||||
} |
|
||||
|
|
||||
public double getLength(){ |
|
||||
double result = 0; |
|
||||
for (int i = 0; i < this.points.size(); i++){ |
|
||||
Point u = points.get(i); |
|
||||
Point v = (i+1==this.points.size()) ? points.get(0): points.get(i+1); |
|
||||
double m = u.x - v.x; |
|
||||
double n = u.y - v.y; |
|
||||
result += Math.sqrt(m * m + n * n); |
|
||||
} |
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
} |
|
Loading…
Reference in new issue