org.danilopianini.lang.QuadTree Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javalib Show documentation
Show all versions of javalib Show documentation
Handful shortcuts for Java programming
/*
* Copyright (C) 2010-2014, Danilo Pianini and contributors
* listed in the project's pom.xml file.
*
* This file is part of Alchemist, and is distributed under the terms of
* the GNU General Public License, with a linking exception, as described
* in the file LICENSE in the Alchemist distribution's top directory.
*/
package org.danilopianini.lang;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author matheusdev
* @author Danilo Pianini
*
* @param
*/
public class QuadTree implements Serializable {
private static final long serialVersionUID = -8765593946059102012L;
private QuadTree botLeft;
private QuadTree botRight;
private final Rectangle2D bounds;
private final List> elements;
private final int elems;
private final double maxX;
private final double maxY;
private final double minX;
private final double minY;
private QuadTree topLeft;
private QuadTree topRight;
private static class QuadTreeEntry implements Serializable {
private static final long serialVersionUID = 9021533648086596986L;
private final E element;
private double x, y;
public QuadTreeEntry(final E el, final double xp, final double yp) {
element = el;
x = xp;
y = yp;
}
public String toString() {
return element.toString();
}
}
/**
* @param x
* minimum x
* @param y
* minimum y
* @param mx
* maximum x
* @param my
* maximum y
* @param elemPerQuad
* maximum number of elements per quad
*/
public QuadTree(final double x, final double y, final double mx, final double my, final int elemPerQuad) {
bounds = new Rectangle2D.Double();
minX = x;
maxX = mx;
minY = y;
maxY = my;
bounds.setFrameFromDiagonal(minX, minY, maxX, maxY);
elements = new ArrayList<>(elemPerQuad);
elems = elemPerQuad;
}
private boolean contains(final double x, final double y) {
return y >= minY && y <= maxY && x >= minX && x <= maxX;
}
/**
* Deletes an element from the QuadTree.
*
* @param e
* The element to delete
* @param x
* the x position of the element
* @param y
* the y position of the element
* @return true if the element is found and removed
*/
public boolean delete(final E e, final double x, final double y) {
if (!contains(x, y)) {
return false;
}
if (remove(e, x, y)) {
return true;
} else {
if (topRight.delete(e, x, y)) {
return true;
}
if (topLeft.delete(e, x, y)) {
return true;
}
if (botRight.delete(e, x, y)) {
return true;
}
if (botLeft.delete(e, x, y)) {
return true;
}
}
return false;
}
private boolean hasChildren() {
return topLeft != null;
}
/**
* Inserts an element in the QuadTree.
*
* @param e
* The element to add
* @param x
* the x position of the element
* @param y
* the y position of the element
* @return true if the element is correctly inserted, false otherwise (e.g.
* because the element is out of the indexed space)
*/
public boolean insert(final E e, final double x, final double y) {
return insert(new QuadTreeEntry(e, x, y));
}
private boolean insert(final QuadTreeEntry e) {
if (!contains(e.x, e.y)) {
return false;
}
if (set(e)) {
return true;
} else {
subdivide();
if (topRight.insert(e)) {
return true;
}
if (topLeft.insert(e)) {
return true;
}
if (botRight.insert(e)) {
return true;
}
if (botLeft.insert(e)) {
return true;
}
/*
* Point on the bounds of the subsets. Force inclusion here.
*/
elements.add(e);
return true;
}
}
/**
* @return the maximum number of elements per node
*/
public int getMaxElementsNumber() {
return elems;
}
/**
* If an element is moved, updates the QuadTree accordingly.
*
* @param e
* the element
* @param sx
* the start x
* @param sy
* the start y
* @param fx
* the final x
* @param fy
* the final y
* @return true if the element is found and no error occurred
*/
public boolean move(final E e, final double sx, final double sy, final double fx, final double fy) {
if (sx == fx && sy == fy) {
return true;
}
if (!contains(sx, sy)) {
return false;
}
final QuadTree currentContainer = searchForEntry(e, sx, sy);
final int pos = currentContainer.searchAtThisLevel(e, sx, sy);
final QuadTreeEntry entry = currentContainer.elements.get(pos);
entry.x = fx;
entry.y = fy;
if (!currentContainer.contains(fx, fy)) {
/*
* TODO: improve this.
*/
currentContainer.elements.remove(pos);
insert(entry);
}
return true;
}
/**
* @param range
* the range where to retrieve the objects
* @return a list of the objects in the range
*/
public List query(final Rectangle2D range) {
final List result = new ArrayList<>();
query(range, result);
return result;
}
private void query(final Rectangle2D range, final List results) {
if (bounds.intersects(range)) {
for (final QuadTreeEntry entry : elements) {
if (range.contains(entry.x, entry.y)) {
results.add(entry.element);
}
}
if (hasChildren()) {
topLeft.query(range, results);
topRight.query(range, results);
botLeft.query(range, results);
botRight.query(range, results);
}
}
}
private boolean remove(final E e, final double x, final double y) {
for (int i = 0; i < elements.size(); i++) {
final QuadTreeEntry entry = elements.get(i);
if (entry.x == x && entry.y == y && entry.element.equals(e)) {
elements.remove(i);
return true;
}
}
return false;
}
private int searchAtThisLevel(final E e, final double x, final double y) {
for (int i = 0; i < elements.size(); i++) {
final QuadTreeEntry entry = elements.get(i);
if (entry.x == x && entry.y == y && entry.element.equals(e)) {
return i;
}
}
return -1;
}
private QuadTree searchForEntry(final E e, final double sx, final double sy) {
if (!contains(sx, sy)) {
return null;
}
final int index = searchAtThisLevel(e, sx, sy);
if (index >= 0) {
return this;
} else {
if (hasChildren()) {
QuadTree result = topRight.searchForEntry(e, sx, sy);
if (result == null) {
result = topLeft.searchForEntry(e, sx, sy);
if (result == null) {
result = botRight.searchForEntry(e, sx, sy);
if (result == null) {
result = botLeft.searchForEntry(e, sx, sy);
}
}
}
return result;
}
return null;
}
}
private boolean set(final QuadTreeEntry e) {
if (elements.size() >= elems) {
return false;
}
elements.add(e);
return true;
}
/**
*
* Subdivides this Quadtree into 4 other quadtrees.
*
*
* This is usually used, when this Quadtree already has an Element.
*
*
* @return whether this Quadtree was subdivided, or didn't subdivide,
* because it was already subdivided.
*/
protected boolean subdivide() {
if (hasChildren()) {
return false;
}
final double cx = bounds.getCenterX();
final double cy = bounds.getCenterY();
topLeft = new QuadTree(minX, cy, cx, maxY, getMaxElementsNumber());
topRight = new QuadTree(cx, cy, maxX, maxY, getMaxElementsNumber());
botLeft = new QuadTree(minX, minY, cx, cy, getMaxElementsNumber());
botRight = new QuadTree(cx, minY, maxX, cy, getMaxElementsNumber());
return true;
}
@Override
public String toString() {
return bounds.toString() + ' ' + elements.toString();
}
}