/*********************************************************************************** * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * * * * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * * Our goal is to create an emulator which will provide a server for players to * * continue playing a game similar to the one they used to play. We are basing * * it on the final publish of the game prior to end-game events. * * * * This file is part of Holocore. * * * * -------------------------------------------------------------------------------- * * * * Holocore is free software: you can redistribute it and/or modify * * it under the terms of the GNU Affero General Public License as * * published by the Free Software Foundation, either version 3 of the * * License, or (at your option) any later version. * * * * Holocore is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Affero General Public License for more details. * * * * You should have received a copy of the GNU Affero General Public License * * along with Holocore. If not, see . * * * ***********************************************************************************/ package resources.objects.quadtree; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; public class QuadTree { private static final double DEFAULT_RANGE = 1E-5; private final QuadNode headNode; private final double minX, minY, maxX, maxY; private final int quadTreeSize; public QuadTree(int size, double minX, double minY, double maxX, double maxY) { this.quadTreeSize = size; this.minX = minX; this.minY = minY; this.maxX = maxX; this.maxY = maxY; headNode = new QuadNode(minX, minY, maxX, maxY); } public void put(double x, double y, V value) { if (value == null || Double.isNaN(x) || Double.isNaN(y)) return; if (x < minX || y < minY || x > maxX || y > maxY) return; headNode.insert(x, y, value); } public boolean contains(double x, double y, V obj) { return containsWithinRange(x, y, obj, DEFAULT_RANGE); } public boolean containsWithinRange(double x, double y, V obj, double range) { if (Double.isNaN(x) || Double.isNaN(y)) return false; if (x < minX || y < minY || x > maxX || y > maxY) return false; return headNode.containsWithinRange(x, y, obj, range); } 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(); if (Double.isNaN(x) || Double.isNaN(y)) return list; if (x < minX || y < minY || x > maxX || y > maxY) return list; headNode.getWithinRange(list, x, y, range); return list; } public boolean remove(double x, double y, V instance) { if (instance == null || Double.isNaN(x) || Double.isNaN(y)) return false; if (x < minX || y < minY || x > maxX || y > maxY) return false; return headNode.remove(x, y, instance); } public int remove(double x, double y) { if (Double.isNaN(x) || Double.isNaN(y)) return 0; if (x < minX || y < minY || x > maxX || y > maxY) return 0; return remove(x, y, -1); } public int remove(double x, double y, int maxRemove) { return headNode.remove(x, y, maxRemove); } public V getIgnoreCollisions(double x, double y) { return headNode.getIgnoreCollisions(x, y); } private class QuadNode { 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; public QuadNode(double minX, double minY, double maxX, double maxY) { this.minX = minX; this.minY = minY; this.maxX = maxX; this.maxY = maxY; 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 (size == 0 || square(x - centerX) + square(y - centerY) <= DEFAULT_RANGE) { insertNode(x, y, value); } else { if (size == 1 && obj != null) { V v = obj.value; expand(obj.x, obj.y).insert(obj.x, obj.y, v); obj = null; } expand(x, y).insert(x, y, value); } size++; } private double square(double x) { return x * x; } public boolean remove(double x, double y, V instance) { 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; return true; } else return obj.removeInstance(instance); } 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 && square(obj.x - x) + square(obj.y - y) <= DEFAULT_RANGE) return removeAll(maxRemove); int rem = removeRecurse(x, y, maxRemove); size -= rem; return rem; } private int removeRecurse(double x, double y, int maxRemove) { QuadNode quad = subnodes.get(x, y); if (quad != null) return quad.remove(x, y, maxRemove); return 0; } private int removeAll(int maxRemove) { int objSize = obj.size(); if (maxRemove < 0 || maxRemove >= objSize) { obj = null; size -= objSize; return objSize; } obj = obj.get(maxRemove); size -= maxRemove; return maxRemove; } private QuadNode expand(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--; 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) { if (obj == null) obj = new Node(x, y, v, null); else obj.insert(v); } public boolean containsWithinRange(double x, double y, V v, double range) { if (obj != null && square(obj.x - x) + square(obj.y - y) <= square(range)) { return obj.contains(v); } 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.containsWithinRange(x, y, v, range)) return true; } } return false; } 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); } } } public V getIgnoreCollisions(double x, double y) { if (obj != null) return obj.value; QuadNode node = subnodes.get(x, y); if (node == null) return null; return node.getIgnoreCollisions(x, y); } } private class Node { private double x, y; private V value; private Node next; public Node(double x, double y, V value, Node next) { this.x = x; this.y = y; this.value = value; this.next = next; } public void insert(V value) { if (next == null) next = new Node(x, y, value, null); else next.insert(value); } public boolean contains(V v) { if (value.equals(v)) return true; if (next != null) return next.contains(v); return false; } public int addAll(List list) { list.add(value); if (next != null) return 1 + next.addAll(list); return 1; } public Node get(int i) { if (i == 0) return this; if (next == null) return null; return next.get(i - 1); } public int size() { if (next == null) return 1; return 1 + next.size(); } public boolean removeInstance(V instance) { if (next == null) return false; if (next.value == instance || next.value.equals(instance)) { next = next.next; // easy peesy if (next != null) return next.removeInstance(instance); return true; } return next.removeInstance(instance); } } 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() { if (!hasNext()) throw new NoSuchElementException("Reached the end of the iterator!"); 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); } }