All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.danilopianini.lang.QuadTree Maven / Gradle / Ivy

There is a newer version: 5.0.0
Show newest version
/*
 * 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(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy