diff --git a/src/resources/objects/quadtree/QuadTree.java b/src/resources/objects/quadtree/QuadTree.java index c4db8e801..10f0c4713 100644 --- a/src/resources/objects/quadtree/QuadTree.java +++ b/src/resources/objects/quadtree/QuadTree.java @@ -32,12 +32,14 @@ import java.util.List; public class QuadTree { - private static final double DEFAULT_RANGE = 1E-5; + private static final double DEFAULT_RANGE = 1E-5; - private QuadNode headNode; - private double minX, minY, maxX, maxY; + private final QuadNode headNode; + private final double minX, minY, maxX, maxY; + private final int quadTreeSize; - public QuadTree(double minX, double minY, double maxX, double maxY) { + public QuadTree(int size, double minX, double minY, double maxX, double maxY) { + this.quadTreeSize = size; this.minX = minX; this.minY = minY; this.maxX = maxX; @@ -53,12 +55,12 @@ public class QuadTree { headNode.insert(x, y, value); } - public List get(double x, double y) { + public List get(double x, double y) { return getWithinRange(x, y, DEFAULT_RANGE); } - public List getWithinRange(double x, double y, double range) { - List list = new ArrayList(); + public List getWithinRange(double x, double y, double range) { + List list = new ArrayList(); if (Double.isNaN(x) || Double.isNaN(y)) return list; if (x < minX || y < minY || x > maxX || y > maxY) @@ -92,11 +94,9 @@ public class QuadTree { } private class QuadNode { - private QuadNode topLeft = null; // min, min - private QuadNode topRight = null; // max, min - private QuadNode bottomRight = null; // max, max - private QuadNode bottomLeft = null; // min, max - private double minX, minY, maxX, maxY, centerX, centerY; + private final QuadSubNodes subnodes; + private final double minX, minY, maxX, maxY, centerX, centerY; + private final double cellWidth, cellHeight; private Node obj = null; private int size = 0; @@ -105,13 +105,16 @@ public class QuadTree { this.minY = minY; this.maxX = maxX; this.maxY = maxY; - this.centerX = (minX+maxX) / 2; - this.centerY = (minY+maxY) / 2; + this.centerX = (minX + maxX) / 2; + this.centerY = (minY + maxY) / 2; this.obj = null; + this.subnodes = new QuadSubNodes<>(quadTreeSize, minX, minY, maxX, maxY); + this.cellWidth = (maxX - minX) / quadTreeSize; + this.cellHeight = (maxY - minY) / quadTreeSize; } public void insert(double x, double y, V value) { - if (Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2) <= DEFAULT_RANGE || size == 0) { + if (size == 0 || square(x - centerX) + square(y - centerY) <= DEFAULT_RANGE) { insertNode(x, y, value); } else { if (size == 1 && obj != null) { @@ -124,8 +127,12 @@ public class QuadTree { size++; } + private double square(double x) { + return x * x; + } + public boolean remove(double x, double y, V instance) { - if (obj != null && Math.sqrt(Math.pow(obj.x-x,2) + Math.pow(obj.y-y,2)) <= DEFAULT_RANGE) { + if (obj != null && square(obj.x - x) + square(obj.y - y) <= DEFAULT_RANGE) { if (obj.size() == 1 && (obj.value == instance || obj.value.equals(instance))) { size -= 1; obj = null; @@ -133,14 +140,14 @@ public class QuadTree { } else return obj.removeInstance(instance); } - QuadNode quad = getQuadrant(x, y); + QuadNode quad = subnodes.get(x, y); if (quad != null) return quad.remove(x, y, instance); return false; } public int remove(double x, double y, int maxRemove) { - if (obj != null && Math.sqrt(Math.pow(obj.x-x,2) + Math.pow(obj.y-y,2)) <= DEFAULT_RANGE) + if (obj != null && square(obj.x - x) + square(obj.y - y) <= DEFAULT_RANGE) return removeAll(maxRemove); int rem = removeRecurse(x, y, maxRemove); size -= rem; @@ -148,7 +155,7 @@ public class QuadTree { } private int removeRecurse(double x, double y, int maxRemove) { - QuadNode quad = getQuadrant(x, y); + QuadNode quad = subnodes.get(x, y); if (quad != null) return quad.remove(x, y, maxRemove); return 0; @@ -166,49 +173,21 @@ public class QuadTree { return maxRemove; } - private QuadNode getQuadrant(double x, double y) { - if (x < centerX) { - if (y < centerY) { - if (topLeft != null) - return topLeft; - } else { - if (bottomLeft != null) - return bottomLeft; - } - } else { - if (y < centerY) { - if (topRight != null) - return topRight; - } else { - if (bottomRight != null) - return bottomRight; - } - } - return null; - } - private QuadNode expand(double x, double y) { - if (x <= centerX) { - if (y <= centerY) { // top-left - if (topLeft == null) - topLeft = new QuadNode(minX, minY, centerX, centerY); - return topLeft; - } else { // bottom-left - if (bottomLeft == null) - bottomLeft = new QuadNode(minX, centerY, centerX, maxY); - return bottomLeft; - } - } else { - if (y <= centerY) { // top-right - if (topRight == null) - topRight = new QuadNode(centerX, minY, maxX, centerY); - return topRight; - } else { // bottom-right - if (bottomRight == null) - bottomRight = new QuadNode(centerX, centerY, maxX, maxY); - return bottomRight; - } + int indX = getIndex(x, minX, cellWidth); + int indY = getIndex(y, minY, cellHeight); + if (indX >= subnodes.length()) + indX--; + if (indY >= subnodes.length()) + indY--; + QuadNode node = subnodes.getSubnode(indX, indY); + if (node == null) { + double mnX = minX + indX * cellWidth; + double mnY = minY + indY * cellHeight; + node = new QuadNode(mnX, mnY, mnX + cellWidth, mnY + cellHeight); + subnodes.setSubnode(indX, indY, node); } + return node; } private void insertNode(double x, double y, V v) { @@ -218,75 +197,45 @@ public class QuadTree { obj.insert(v); } - public void getWithinRange(List list, double x, double y, double range) { - if (obj != null && Math.sqrt(Math.pow(obj.x-x,2) + Math.pow(obj.y-y,2)) <= range) { + public void getWithinRange(List list, double x, double y, double range) { + if (obj != null && square(obj.x - x) + square(obj.y - y) <= square(range)) { obj.addAll(list); + if (size == obj.size()) + return; + } + int mnX = (x - range <= minX) ? 0 : getIndex(x - range, minX, cellWidth); + int mnY = (y - range <= minY) ? 0 : getIndex(y - range, minY, cellHeight); + int mxX = (x + range >= maxX) ? subnodes.length() - 1 : getIndex(x + range, minX, cellWidth); + int mxY = (y + range >= maxY) ? subnodes.length() - 1 : getIndex(y + range, minY, cellHeight); + for (int xInd = mnX; xInd <= mxX; xInd++) { + for (int yInd = mnY-1; yInd <= mxY; yInd++) { + if (yInd < 0) + continue; + QuadNode node = subnodes.getSubnode(xInd, yInd); + if (node != null) + node.getWithinRange(list, x, y, range); + } } - if (topLeft != null && topLeft.intersects(x, y, range)) - topLeft.getWithinRange(list, x, y, range); - if (topRight != null && topRight.intersects(x, y, range)) - topRight.getWithinRange(list, x, y, range); - if (bottomRight != null && bottomRight.intersects(x, y, range)) - bottomRight.getWithinRange(list, x, y, range); - if (bottomLeft != null && bottomLeft.intersects(x, y, range)) - bottomLeft.getWithinRange(list, x, y, range); } public V getIgnoreCollisions(double x, double y) { if (obj != null) return obj.value; - if (x < centerX) { - if (y < centerY) { - if (topLeft == null) - return null; - return topLeft.getIgnoreCollisions(x, y); - } else { - if (bottomLeft == null) - return null; - return bottomLeft.getIgnoreCollisions(x, y); - } - } else { - if (y < centerY) { - if (topRight == null) - return null; - return topRight.getIgnoreCollisions(x, y); - } else { - if (bottomRight == null) - return null; - return bottomRight.getIgnoreCollisions(x, y); - } - } + QuadNode node = subnodes.get(x, y); + if (node == null) + return null; + return node.getIgnoreCollisions(x, y); } - private boolean intersects(double x, double y, double range) { - if (x < minX) { - if (y < minY) - return distance(x, y, minX, minY) <= range; - else if (y > maxY) - return distance(x, y, minX, maxY) <= range; - return x >= minX-range; - } else if (x > maxX) { - if (y < minY) - return distance(x, y, maxX, minY) <= range; - else if (y > maxY) - return distance(x, y, maxX, maxY) <= range; - return x <= maxX+range; - } - return y >= minY-range && y <= maxY+range; - } - - private double distance(double x1, double y1, double x2, double y2) { - return Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)); - } } private class Node { - private double x, y; - private V value; - private Node next; + private double x, y; + private V value; + private Node next; - public Node (double x, double y, V value, Node next) { + public Node(double x, double y, V value, Node next) { this.x = x; this.y = y; this.value = value; @@ -300,7 +249,7 @@ public class QuadTree { next.insert(value); } - public int addAll(List list) { + public int addAll(List list) { list.add(value); if (next != null) return 1 + next.addAll(list); @@ -312,7 +261,7 @@ public class QuadTree { return this; if (next == null) return null; - return next.get(i-1); + return next.get(i - 1); } public int size() { @@ -335,4 +284,83 @@ public class QuadTree { } + private static class QuadSubNodes implements Iterable { + private final T [][] subnodes; + private final double minX, minY; + private final double cellWidth, cellHeight; + + @SuppressWarnings("unchecked") + public QuadSubNodes(int size, double minX, double minY, double maxX, double maxY) { + this.minX = minX; + this.minY = minY; + this.cellWidth = (maxX - minX) / size; + this.cellHeight = (maxY - minY) / size; + this.subnodes = (T [][]) new Object[size][size]; + } + + public T get(double x, double y) { + int indX = getIndex(x, minX, cellWidth); + int indY = getIndex(y, minY, cellHeight); + if (indX >= subnodes.length) + indX--; + if (indY >= subnodes.length) + indY--; + return subnodes[indX][indY]; + } + + public T getSubnode(int x, int y) { + return subnodes[x][y]; + } + + public void setSubnode(int x, int y, T node) { + subnodes[x][y] = node; + } + + public int length() { + return subnodes.length; + } + + @Override + public Iterator iterator() { + return new Iterator(); + } + + public class Iterator implements java.util.Iterator { + + private int x = 0; + private int y = 0; + + @Override + public boolean hasNext() { + return x < subnodes.length && y < subnodes.length; + } + + @Override + public T next() { + T node = subnodes[x][y]; + if (y + 1 < subnodes.length) + y++; + else { + x++; + y = 0; + } + return node; + } + + @Override + public void remove() { + if (y == 0) { + subnodes[x - 1][subnodes.length - 1] = null; + } else { + subnodes[x][y - 1] = null; + } + } + + } + } + + private static int getIndex(double pos, double min, double width) { + return (int) ((pos - min) / width); + } + } diff --git a/src/services/objects/ObjectAwareness.java b/src/services/objects/ObjectAwareness.java index 58c3a7400..d6adc765a 100644 --- a/src/services/objects/ObjectAwareness.java +++ b/src/services/objects/ObjectAwareness.java @@ -28,7 +28,7 @@ public class ObjectAwareness { private void loadQuadTree() { for (Terrain t : Terrain.values()) { - quadTree.put(t, new QuadTree(-8192, -8192, 8192, 8192)); + quadTree.put(t, new QuadTree(16, -8192, -8192, 8192, 8192)); } } diff --git a/test/resources/objects/quadtree/TestQuadTree.java b/test/resources/objects/quadtree/TestQuadTree.java index bf845c3f9..443546cd7 100644 --- a/test/resources/objects/quadtree/TestQuadTree.java +++ b/test/resources/objects/quadtree/TestQuadTree.java @@ -40,7 +40,7 @@ public class TestQuadTree { @Test public void testInsertGet() { - QuadTree tree = new QuadTree(0, 0, 10, 10); + QuadTree tree = new QuadTree(2, 0, 0, 10, 10); tree.put(5, 5, 5.0); Assert.assertEquals(5, (double)tree.getIgnoreCollisions(5, 5), 1E-10); tree.put(0, 0, 10.0); @@ -67,7 +67,7 @@ public class TestQuadTree { @Test public void testDuplicatesBug() { - QuadTree tree = new QuadTree(0, 0, 10, 10); + QuadTree tree = new QuadTree(2, 0, 0, 10, 10); Double d = new Double(5.0); tree.put(5.0, 5.0, d); Assert.assertEquals(1, tree.getWithinRange(5.0, 5.0, 10).size()); @@ -97,12 +97,13 @@ public class TestQuadTree { @Test public void testWithinArea() { List points = new ArrayList(); - QuadTree tree = new QuadTree(0, 0, 10, 10); + QuadTree tree = new QuadTree(4, 0, 0, 10, 10); + Point2D p; for (double x = 0; x < 10; x += 0.15) { for (double y = 0; y < 10; y += 0.15) { x = ((int)(x*100))/100.0; y = ((int)(y*100))/100.0; - Point2D p = new Point2D(x, y); + p = new Point2D(x, y); points.add(p); tree.put(x, y, p); } @@ -118,6 +119,25 @@ public class TestQuadTree { } } + @Test + public void testQuadLarge() { + List points = new ArrayList(); + QuadTree tree = new QuadTree(16, -8192, -8192, 8192, 8192); + Point2D p; + for (int x = -2000; x < 2000; x += 25) { + for (int y = -2000; y < 2000; y += 25) { + p = new Point2D(x, y); + points.add(p); + tree.put(x, y, p); + } + } + for (int x = -2000; x < 2000; x += 50) { + for (int y = -2000; y < 2000; y += 50) { + test(points, tree, x, y, 200); + } + } + } + @Test public void testIntersectRandom() { double minX = 4; @@ -170,7 +190,7 @@ public class TestQuadTree { Assert.assertEquals("Failed at ("+x+", 10)", Math.sqrt((10-maxY)*(10-maxY)+Math.pow(x-nearest,2))<=range, inter); } } - + private boolean intersects(double minX, double minY, double maxX, double maxY, double x, double y, double range) { if (x < minX) { if (y < minY) @@ -189,25 +209,30 @@ public class TestQuadTree { } private double distance(double x1, double y1, double x2, double y2) { - return Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)); + return Math.sqrt(square(x1-x2) + square(y1-y2)); } - private void test(List points, QuadTree tree, double x, double y, double range) { + private int test(List points, QuadTree tree, double x, double y, double range) { int expected = getExpectedWithinRange(points, x, y, range); int actual = tree.getWithinRange(x, y, range).size(); Assert.assertEquals("Failed at (" + x + ", " + y + ")", expected, actual); + return actual; } private int getExpectedWithinRange(List points, double x, double y, double range) { int count = 0; for (Point2D p : points) { - if (Math.sqrt(Math.pow(p.x-x, 2) + Math.pow(p.y-y, 2)) <= range) { + if (square(p.x-x) + square(p.y-y) <= square(range)) { count++; } } return count; } + private double square(double x) { + return x * x; + } + private class Point2D { private double x; private double y;