package org.geogebra.common.util.clipper;
import java.util.ArrayList;
import java.util.List;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.util.clipper.Point.DoublePoint;
public abstract class ClipperBase implements Clipper {
/**
* modified to be compatible with double
*/
protected static class LocalMinima {
double y;
Edge leftBound;
Edge rightBound;
LocalMinima next;
}
/**
* modified to be compatible with double
*/
protected static class Scanbeam {
double y;
Scanbeam next;
}
/**
* modified to be compatible with double
*/
private static void initEdge(Edge e, Edge eNext, Edge ePrev,
DoublePoint pt) {
e.next = eNext;
e.prev = ePrev;
e.setCurrent(new DoublePoint(pt));
e.outIdx = Edge.UNASSIGNED;
}
/**
* modified to be compatible with double
*/
private static void initEdge2(Edge e, PolyType polyType) {
if (e.getCurrent().getY() >= e.next.getCurrent().getY()) {
e.setBot(new DoublePoint(e.getCurrent()));
e.setTop(new DoublePoint(e.next.getCurrent()));
} else {
e.setTop(new DoublePoint(e.getCurrent()));
e.setBot(new DoublePoint(e.next.getCurrent()));
}
e.updateDeltaX();
e.polyTyp = polyType;
}
/**
* modified to be compatible with double
*/
private static void rangeTest(DoublePoint 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 double LOW_RANGE = 0x3FFFFFFF;
private final static double HI_RANGE = 0x3FFFFFFFFFFFFFFFL;
protected LocalMinima minimaList;
protected LocalMinima currentLM;
private final List<List<Edge>> edges;
protected boolean hasOpenPaths;
protected final boolean preserveCollinear;
protected ClipperBase(boolean preserveCollinear) // constructor (nb: no
// external
// instantiation)
{
this.preserveCollinear = preserveCollinear;
minimaList = null;
currentLM = null;
hasOpenPaths = false;
edges = new ArrayList<List<Edge>>();
}
@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> edges1 = new ArrayList<Edge>(highI + 1);
for (int i = 0; i <= highI; i++) {
edges1.add(new Edge());
}
boolean IsFlat = true;
// 1. Basic (first) edge initialization ...
// edges.get( 1 ).setCurrent( new LongPoint( pg.get( 1 ) ) );
edges1.get(1).setCurrent(new DoublePoint(pg.get(1)));
rangeTest(pg.get(0));
rangeTest(pg.get(highI));
initEdge(edges1.get(0), edges1.get(1), edges1.get(highI), pg.get(0));
initEdge(edges1.get(highI), edges1.get(0), edges1.get(highI - 1),
pg.get(highI));
for (int i = highI - 1; i >= 1; --i) {
rangeTest(pg.get(i));
initEdge(edges1.get(i), edges1.get(i + 1), edges1.get(i - 1),
pg.get(i));
}
Edge eStart = edges1.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;
if (e.prev.getBot().getX() < e.prev.getTop().getX()) {
e.prev.reverseHorizontal();
}
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;
while (e.next.outIdx != Edge.SKIP) {
e.nextInLML = e.next;
if (e.getBot().getX() != e.prev.getTop().getX()) {
e.reverseHorizontal();
}
e = e.next;
}
insertLocalMinima(locMin);
this.edges.add(edges1);
return true;
}
this.edges.add(edges1);
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 ppg, PolyType polyType, boolean closed) {
boolean result = false;
for (int i = 0; i < ppg.size(); ++i) {
if (addPath(ppg.get(i), polyType, closed)) {
result = true;
}
}
return result;
}
@Override
public void clear() {
disposeLocalMinimaList();
edges.clear();
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;
}
}
public boolean isPreserveCollinear() {
return preserveCollinear;
}
protected void popLocalMinima() {
if (currentLM == null) {
return;
}
currentLM = currentLM.next;
}
private Edge processBound(Edge e0, boolean LeftBoundIsForward) {
Edge e = e0;
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 && equalsEdgeHorizontal(e.deltaX)) {
e = e.prev;
}
} else {
while (e.getTop().getY() == e.prev.getBot().getY()) {
e = e.prev;
}
while (e != result && equalsEdgeHorizontal(e.deltaX)) {
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 (equalsEdgeHorizontal(e.deltaX)) {
// 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.outIdx != Edge.SKIP) {
if (equalsEdgeHorizontal(EStart.deltaX)) // 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 (equalsEdgeHorizontal(result.deltaX)
&& 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 (equalsEdgeHorizontal(Horz.prev.deltaX)) {
Horz = Horz.prev;
}
if (Horz.prev.getTop().getX() == result.next.getTop().getX()) {
// removed, condition never satisfied
// TODO: check if something else was intended
// if (!LeftBoundIsForward) {
// result = Horz.prev;
// }
} else if (Horz.prev.getTop().getX() > result.next.getTop()
.getX()) {
result = Horz.prev;
}
}
while (e != result) {
e.nextInLML = e.next;
if (equalsEdgeHorizontal(e.deltaX) && e != EStart
&& e.getBot().getX() != e.prev.getTop().getX()) {
e.reverseHorizontal();
}
e = e.next;
}
if (equalsEdgeHorizontal(e.deltaX) && 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 (equalsEdgeHorizontal(result.deltaX)
&& result.prev.outIdx != Edge.SKIP) {
Horz = result;
while (equalsEdgeHorizontal(Horz.next.deltaX)) {
Horz = Horz.next;
}
if (Horz.next.getTop().getX() == result.prev.getTop().getX()) {
if (!LeftBoundIsForward) {
result = Horz.next;
}
} else if (Horz.next.getTop().getX() > result.prev.getTop()
.getX()) {
result = Horz.next;
}
}
while (e != result) {
e.nextInLML = e.prev;
if (equalsEdgeHorizontal(e.deltaX) && e != EStart
&& e.getBot().getX() != e.next.getTop().getX()) {
e.reverseHorizontal();
}
e = e.prev;
}
if (equalsEdgeHorizontal(e.deltaX) && e != EStart
&& e.getBot().getX() != e.next.getTop().getX()) {
e.reverseHorizontal();
}
result = result.prev; // move to the edge just beyond current bound
}
return result;
}
private static boolean equalsEdgeHorizontal(double d) {
return MyDouble.exactEqual(d, Edge.HORIZONTAL);
}
protected void reset() {
currentLM = minimaList;
if (currentLM == null) {
return; // ie nothing to process
}
// reset all edges ...
LocalMinima lm = minimaList;
while (lm != null) {
Edge e = lm.leftBound;
if (e != null) {
// e.setCurrent( new LongPoint( e.getBot() ) );
e.setCurrent(new DoublePoint(e.getBot()));
e.side = Edge.Side.LEFT;
e.outIdx = Edge.UNASSIGNED;
}
e = lm.rightBound;
if (e != null) {
// e.setCurrent( new LongPoint( e.getBot() ) );
e.setCurrent(new DoublePoint(e.getBot()));
e.side = Edge.Side.RIGHT;
e.outIdx = Edge.UNASSIGNED;
}
lm = lm.next;
}
}
}