package cz.urbangaming.galgs.algorithms.kdtree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.opengl.GLES20;
import android.util.Pair;
import cz.urbangaming.galgs.utils.Point2D;
import cz.urbangaming.galgs.utils.RectangularArea;
import cz.urbangaming.galgs.utils.Utils;
import cz.urbangaming.galgs.utils.XYOrderComparator;
import cz.urbangaming.galgs.utils.YXOrderComparator;
/**
*
* @author Michal Karm Babacek
* @license GNU GPL 3.0
*
* Credit:
* Computational Geometry, Algorithms and Applications
* de Berg, http://www.cs.uu.nl/geobook/
* Chapter 5, Orthogonal search
*/
public class KDTree {
private static KDTree instance = null;
private KDTree() {
}
/**
* We ain't wanna more instances lying around...
*
* @return
*/
public static synchronized KDTree getInstance() {
if (instance == null) {
instance = new KDTree();
}
return instance;
}
//Let's do some coloring, shall we?
//Like two sets of Lines...?
enum KDNodeType {
VERTICAL_SPLIT_TYPE, HORIZONTAL_SPLIT_TYPE
}
private RectangularArea pointsArea;
private KDNode treeRoot;
private List<Point2D> linesPoints = new ArrayList<Point2D>();
public KDTree(int width, int height) {
pointsArea = new RectangularArea(0, 0, width, height);
}
private KDNode build(List<Point2D> points) {
Point2D middlePoint = Utils.getMiddlePoint(points);
treeRoot = new KDNode(null, KDNodeType.VERTICAL_SPLIT_TYPE, middlePoint, 0);
linesPoints.add(new Point2D(middlePoint.x(), pointsArea.y()));
linesPoints.add(new Point2D(middlePoint.x(), pointsArea.endY()));
treeRoot.leftChild = build(new RectangularArea(0, 0, middlePoint.x(), pointsArea.endY()), 1, treeRoot, Utils.getLeftPoints(points));
treeRoot.rightChild = build(new RectangularArea(middlePoint.x(), 0, pointsArea.endX(), pointsArea.endY()), 1, treeRoot, Utils.getRightPoints(points));
return treeRoot;
}
private KDNode build(RectangularArea rectArea, int treeDepth, KDNode parent, List<Point2D> points) {
// We ain't got no point, bro!
if (points.size() == 0) {
return null;
}
Collections.sort(points, treeDepth % 2 == 0 ? new XYOrderComparator() : new YXOrderComparator());
Point2D middlePoint = Utils.getMiddlePoint(points);
KDNode head = new KDNode(parent, treeDepth % 2 == 0 ? KDNodeType.VERTICAL_SPLIT_TYPE : KDNodeType.HORIZONTAL_SPLIT_TYPE, middlePoint, treeDepth);
if (treeDepth % 2 == 0) {
// Splitting it vertically
linesPoints.add(new Point2D(middlePoint.x(), rectArea.y()));
linesPoints.add(new Point2D(middlePoint.x(), rectArea.endY()));
} else {
// Splitting it horizontally
linesPoints.add(new Point2D(rectArea.x(), middlePoint.y()));
linesPoints.add(new Point2D(rectArea.endX(), middlePoint.y()));
}
if (points.size() == 1) {
// We've got just the head...
return head;
}
if (treeDepth % 2 == 0) { // Split vertically
head.leftChild = build(new RectangularArea(rectArea.x(), rectArea.y(), middlePoint.x(), rectArea.endY(), true), treeDepth + 1, head, Utils.getLeftPoints(points));
head.rightChild = build(new RectangularArea(middlePoint.x(), rectArea.y(), rectArea.endX(), rectArea.endY(), true), treeDepth + 1, head, Utils.getRightPoints(points));
} else { // Split horizontally
head.leftChild = build(new RectangularArea(rectArea.x(), rectArea.y(), rectArea.endX(), middlePoint.y(), true), treeDepth + 1, head, Utils.getLeftPoints(points));
head.rightChild = build(new RectangularArea(rectArea.x(), middlePoint.y(), rectArea.endX(), rectArea.endY(), true), treeDepth + 1, head, Utils.getRightPoints(points));
}
return head;
}
public Pair<List<Point2D>, Integer> buildKDTree(List<Point2D> vertices) {
Collections.sort(vertices, new XYOrderComparator());
build(vertices);
return new Pair<List<Point2D>, Integer>(linesPoints, GLES20.GL_LINES);
}
}