package util; /** * This structure should be useful for large 2D worlds. It can find objects in a * certain area very fast (via * {@link QuadTree#findInArea(ResultListener, float, float, float) e.g.}) * * @param <T> */ public class QuadTree<T> { private TreeNode myRootNode; private int itemCount; private class TreeNode { float x, y; T myValue; TreeNode quadrant1, quadrant2, quadrant3, quadrant4; private TreeNode(float x, float y, T value) { this.x = x; this.y = y; myValue = value; } } public abstract class ResultListener { public abstract void onResult(T myValue); } public void clear() { myRootNode = null; } public void getAllItems(ResultListener r) { getAllItems(r, myRootNode); } private void getAllItems(ResultListener r, TreeNode node) { if (node != null) { r.onResult(node.myValue); if (node.quadrant1 != null) getAllItems(r, node.quadrant1); if (node.quadrant2 != null) getAllItems(r, node.quadrant2); if (node.quadrant3 != null) getAllItems(r, node.quadrant3); if (node.quadrant4 != null) getAllItems(r, node.quadrant4); } } /** * @param newXPos * @param newYPos * @param value * @return true if the position was updated and false if the node could not * be found */ public boolean updatePosFor(float newXPos, float newYPos, T value) { if (remove(value)) { add(newXPos, newYPos, value); return true; } return false; } /** * @param value * @return true if item was found and removed */ public boolean remove(T value) { return findValueEntry(myRootNode, value, true); } public boolean contains(T value) { return findValueEntry(myRootNode, value, false); } private boolean findValueEntry(TreeNode node, T value, boolean removeWhenFound) { if (node != null && node.myValue.equals(value)) { if (removeWhenFound) node.myValue = null; return true; } else { boolean result = false; if (node.quadrant1 != null) result |= findValueEntry(node.quadrant1, value, removeWhenFound); if (!result && node.quadrant2 != null) result |= findValueEntry(node.quadrant2, value, removeWhenFound); if (!result && node.quadrant3 != null) result |= findValueEntry(node.quadrant3, value, removeWhenFound); if (!result && node.quadrant4 != null) result |= findValueEntry(node.quadrant4, value, removeWhenFound); return result; } } public void add(float x, float y, T value) { itemCount++; myRootNode = add(myRootNode, x, y, value); } /** * @return the number of already added elements */ public int size() { return itemCount; } private TreeNode add(TreeNode node, float x, float y, T value) { if (node == null) return new TreeNode(x, y, value); else if (node.myValue == null && canBeInsertedHere(node, x, y)) { node.myValue = value; return node; } // if dublicate objects at the same <x,y> coords should not be allowed, // add this line: // if (x == node.x && y == node.y) node.myValue = value; else if (x < node.x && y < node.y) node.quadrant3 = add(node.quadrant3, x, y, value); else if (x < node.x && y >= node.y) node.quadrant2 = add(node.quadrant2, x, y, value); else if (x >= node.x && y < node.y) node.quadrant4 = add(node.quadrant4, x, y, value); else if (x >= node.x && y >= node.y) node.quadrant1 = add(node.quadrant1, x, y, value); return node; } private boolean canBeInsertedHere(TreeNode node, float x, float y) { /* * TODO would the following work? the idea is to fill the gaps which are * created by updatePosFor(..). Is it sufficient to only test the direct * sub-nodes of the tree or would a complete sub-node traversal be * necessary? */ // if ((node.quadrant1 == null) // || (node.quadrant1.x > x && node.quadrant1.y > y)) // if ((node.quadrant2 == null) // || (node.quadrant2.x < x && node.quadrant2.y > y)) // if ((node.quadrant3 == null) // || (node.quadrant3.x < x && node.quadrant3.y < y)) // if ((node.quadrant4 == null) // || (node.quadrant4.x > x && node.quadrant4.y < y)) // return true; return false; } /** * much more efficient then checking for a circular area! * * @param resultListener * A resultListener can be created like this: * "QuadTree< hereTheElementType>.ResultListener l = quadTreeInstance.new ResultListener(){..." * @param xMin * @param xMax * @param yMin * @param yMax */ public void findInArea(ResultListener resultListener, float xMin, float xMax, float yMin, float yMax) { findInArea(resultListener, myRootNode, xMin, xMax, yMin, yMax); } public void findInArea(ResultListener resultListener, float xCenter, float yCenter, float squareSize) { squareSize /= 2; findInArea(resultListener, xCenter - squareSize, xCenter + squareSize, yCenter - squareSize, yCenter + squareSize); } private void findInArea(ResultListener resultListener, TreeNode node, float xMin, float xMax, float yMin, float yMax) { if (node == null) return; if (node.x >= xMin && node.x <= xMax && node.y >= yMin && node.y <= yMax) { if (node.myValue != null) resultListener.onResult(node.myValue); } if (xMin < node.x && yMin < node.y) findInArea(resultListener, node.quadrant3, xMin, xMax, yMin, yMax); if (xMin < node.x && yMax >= node.y) findInArea(resultListener, node.quadrant2, xMin, xMax, yMin, yMax); if (xMax >= node.x && yMin < node.y) findInArea(resultListener, node.quadrant4, xMin, xMax, yMin, yMax); if (xMax >= node.x && yMax >= node.y) findInArea(resultListener, node.quadrant1, xMin, xMax, yMin, yMax); } }