package org.geogebra.common.kernel.implicit;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.MyPoint;
import org.geogebra.common.kernel.SegmentType;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.kernelND.GeoPointND;
/**
* Base class for quadtree algorithms
*/
abstract class QuadTree {
/**
*
*/
private final GeoImplicitCurve geoImplicitCurve;
/**
* All corners are inside / outside
*/
public static final int T0000 = 0;
/**
* only bottom left corner is inside / outside
*/
public static final int T0001 = 1;
/**
* bottom right corner is inside / outside
*/
public static final int T0010 = 2;
/**
* both corners at the bottom are inside / outside
*/
public static final int T0011 = 3;
/**
* top left corner is inside / outside
*/
public static final int T0100 = 4;
/**
* opposite corners are inside / outside. NOTE: This configuration is
* regarded as invalid
*/
public static final int T0101 = 5;
/**
* both the corners at the left are inside / outside
*/
public static final int T0110 = 6;
/**
* only top left corner is inside / outside
*/
public static final int T0111 = 7;
/**
* invalid configuration. expression value is undefined / infinity for at
* least one of the corner
*/
public static final int T_INV = -1;
public static final int EMPTY = 0;
public static final int FINISHED = Integer.MAX_VALUE;
public static final int VALID = 1;
/**
* it would be better to adjust LIST_THRESHOLD based on platform
*/
public int LIST_THRESHOLD = 48;
protected double x;
protected double y;
protected double w;
protected double h;
protected double scaleX;
protected double scaleY;
protected ArrayList<MyPoint> locusPoints;
private LinkedList<PointList> openList = new LinkedList<PointList>();
private MyPoint[] pts = new MyPoint[2];
private PointList p1, p2;
private MyPoint temp;
private ListIterator<PointList> itr1, itr2;
public QuadTree(GeoImplicitCurve geoImplicitCurve) {
this.geoImplicitCurve = geoImplicitCurve;
}
public int config(Rect r) {
int config = 0;
for (int i = 0; i < 4; i++) {
config = (config << 1) | sign(r.evals[i]);
}
return config >= 8 ? (~config) & 0xf : config;
}
/**
*
* @param val
* value to check
* @return the sign depending on the value. if value is infinity or NaN it
* returns T_INV, otherwise it returns 1 for +ve value 0 otherwise
*/
public int sign(double val) {
if (Double.isInfinite(val) || Double.isNaN(val)) {
return T_INV;
} else if (val > 0.0) {
return 1;
} else {
return 0;
}
}
public void abortList() {
itr1 = openList.listIterator();
while (itr1.hasNext()) {
p1 = itr1.next();
locusPoints.add(p1.start);
locusPoints.addAll(p1.pts);
locusPoints.add(p1.end);
}
openList.clear();
}
private static boolean equal(MyPoint q1, MyPoint q2) {
return Kernel.isEqual(q1.x, q2.x, 1e-10)
&& Kernel.isEqual(q1.y, q2.y, 1e-10);
}
public int addSegment(Rect r, int factor) {
int status = createSegment(r, factor);
if (status == VALID) {
if (pts[0].x > pts[1].x) {
temp = pts[0];
pts[0] = pts[1];
pts[1] = temp;
}
itr1 = openList.listIterator();
itr2 = openList.listIterator();
boolean flag1 = false, flag2 = false;
while (itr1.hasNext()) {
p1 = itr1.next();
if (equal(pts[1], p1.start)) {
flag1 = true;
break;
}
}
while (itr2.hasNext()) {
p2 = itr2.next();
if (equal(pts[0], p2.end)) {
flag2 = true;
break;
}
}
if (flag1 && flag2) {
itr1.remove();
p2.mergeTo(p1);
} else if (flag1) {
p1.extendBack(pts[0]);
} else if (flag2) {
p2.extendFront(pts[1]);
} else {
openList.addFirst(new PointList(pts[0], pts[1]));
}
if (openList.size() > LIST_THRESHOLD) {
abortList();
}
}
return status;
}
public int createSegment(Rect r, int factor) {
int gridType = config(r);
if (gridType == T0101 || gridType == T_INV) {
return gridType;
}
double x1 = r.x1(), x2 = r.x2(), y1 = r.y1(), y2 = r.y2();
double tl = r.evals[0], tr = r.evals[1], br = r.evals[2],
bl = r.evals[3];
double q1 = 0.0, q2 = 0.0;
switch (gridType) {
// one or three corners are inside / outside
case T0001:
pts[0] = new MyPoint(x1,
GeoImplicitCurve.interpolate(bl, tl, y2,
y1), SegmentType.MOVE_TO);
pts[1] = new MyPoint(GeoImplicitCurve.interpolate(bl, br, x1, x2),
y2, SegmentType.LINE_TO);
q1 = Math.min(Math.abs(bl), Math.abs(tl));
q2 = Math.min(Math.abs(bl), Math.abs(br));
break;
case T0010:
pts[0] = new MyPoint(x2,
GeoImplicitCurve.interpolate(br, tr, y2,
y1), SegmentType.MOVE_TO);
pts[1] = new MyPoint(GeoImplicitCurve.interpolate(br, bl, x2, x1),
y2, SegmentType.LINE_TO);
q1 = Math.min(Math.abs(br), Math.abs(tr));
q2 = Math.min(Math.abs(br), Math.abs(bl));
break;
case T0100:
pts[0] = new MyPoint(x2,
GeoImplicitCurve.interpolate(tr, br, y1,
y2), SegmentType.MOVE_TO);
pts[1] = new MyPoint(GeoImplicitCurve.interpolate(tr, tl, x2, x1),
y1, SegmentType.LINE_TO);
q1 = Math.min(Math.abs(tr), Math.abs(br));
q2 = Math.min(Math.abs(tr), Math.abs(tl));
break;
case T0111:
pts[0] = new MyPoint(x1,
GeoImplicitCurve.interpolate(tl, bl, y1,
y2), SegmentType.MOVE_TO);
pts[1] = new MyPoint(GeoImplicitCurve.interpolate(tl, tr, x1, x2),
y1, SegmentType.LINE_TO);
q1 = Math.min(Math.abs(bl), Math.abs(tl));
q2 = Math.min(Math.abs(tl), Math.abs(tr));
break;
// two consecutive corners are inside / outside
case T0011:
pts[0] = new MyPoint(x1,
GeoImplicitCurve.interpolate(tl, bl, y1,
y2), SegmentType.MOVE_TO);
pts[1] = new MyPoint(x2,
GeoImplicitCurve.interpolate(tr, br, y1,
y2), SegmentType.LINE_TO);
q1 = Math.min(Math.abs(tl), Math.abs(bl));
q2 = Math.min(Math.abs(tr), Math.abs(br));
break;
case T0110:
pts[0] = new MyPoint(GeoImplicitCurve.interpolate(tl, tr, x1, x2),
y1, SegmentType.MOVE_TO);
pts[1] = new MyPoint(GeoImplicitCurve.interpolate(bl, br, x1, x2),
y2, SegmentType.LINE_TO);
q1 = Math.min(Math.abs(tl), Math.abs(tr));
q2 = Math.min(Math.abs(bl), Math.abs(br));
break;
default:
return EMPTY;
}
// check continuity of the function between P1 and P2
double p = Math.abs(this.geoImplicitCurve
.evaluateImplicitCurve(pts[0].x, pts[0].y, factor));
double q = Math.abs(this.geoImplicitCurve
.evaluateImplicitCurve(pts[1].x, pts[1].y, factor));
if ((p <= q1 && q <= q2)) {
return VALID;
}
return EMPTY;
}
/**
* force to redraw the rectangular area bounded by (startX, startY, startX +
* w, startY + h)
*
* @param startX
* starting x coordinate
* @param startY
* starting y coordinate
* @param width
* width of the rectangular view
* @param height
* height of the rectangular view
* @param slX
* scaleX
* @param slY
* scaleY
*/
public void updatePath(double startX, double startY, double width,
double height, double slX, double slY) {
this.x = startX;
this.y = startY;
this.w = width;
this.h = height;
this.scaleX = slX;
this.scaleY = slY;
this.locusPoints = this.geoImplicitCurve.getLocus().getPoints();
this.updatePath();
this.abortList();
}
public void polishPointOnPath(GeoPointND pt) {
// pt.setUndefined();
}
public List<Coords> probablePoints(GeoImplicitCurve other, int n) {
double xMin = Math.max(x, other.quadTree.x);
double yMin = Math.max(y, other.quadTree.y);
double xMax = Math.min(x + w, other.quadTree.x + w);
double yMax = Math.min(y + h, other.quadTree.y + h);
return GeoImplicitCurve.probableInitialPoints(
this.geoImplicitCurve.getExpression(), other.getExpression(),
xMin, yMin, xMax, yMax, n);
}
public int edgeConfig(Rect r) {
int config = (intersect(r.evals[0], r.evals[1]) << 3)
| (intersect(r.evals[1], r.evals[2]) << 2)
| (intersect(r.evals[2], r.evals[3]) << 1)
| (intersect(r.evals[3], r.evals[0]));
if (config == 15 || config == 0) {
return EMPTY;
}
return config;
}
/**
*
* @param c1
* the value of curve at one of the square vertices
* @param c2
* the value of curve at the other vertex
* @return true if the edge connecting two vertices intersect with curve
* segment
*/
private static int intersect(double c1, double c2) {
if (c1 * c2 <= 0.0) {
return 1;
}
return 0;
}
public abstract void updatePath();
static class PointList {
MyPoint start;
MyPoint end;
LinkedList<MyPoint> pts = new LinkedList<MyPoint>();
public PointList(MyPoint start, MyPoint end) {
this.start = start;
this.end = end;
this.start.setLineTo(false);
this.end.setLineTo(true);
}
public void mergeTo(PointList pl) {
this.pts.addLast(this.end);
if (pl == this) {
MyPoint startCopy = new MyPoint(this.start.x, this.start.y,
SegmentType.LINE_TO);
this.pts.addLast(startCopy);
return;
}
pl.start.setLineTo(true);
this.pts.addLast(pl.start);
this.end = pl.end;
int s1 = this.pts.size(), s2 = pl.pts.size();
if (s2 == 0) {
return;
}
if (s1 < s2) {
ListIterator<MyPoint> itr = this.pts.listIterator(s1 - 1);
while (itr.hasPrevious()) {
pl.pts.addFirst(itr.previous());
}
this.pts = pl.pts;
} else {
ListIterator<MyPoint> itr = pl.pts.listIterator();
while (itr.hasNext()) {
this.pts.addLast(itr.next());
}
}
}
public void extendBack(MyPoint p) {
p.setLineTo(false);
this.start.setLineTo(true);
this.pts.addFirst(start);
this.start = p;
}
public void extendFront(MyPoint p) {
p.setLineTo(true);
this.pts.addLast(this.end);
this.end = p;
}
}
}