package utils.scene.quadtree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class QuadNode<T> {
private Set<QuadLeaf<T>> leafs;
private AtomicBoolean hasChildren = new AtomicBoolean(false);
private QuadNode<T> NW = null;
private QuadNode<T> NE = null;
private QuadNode<T> SE = null;
private QuadNode<T> SW = null;
private final Box bounds;
private AtomicBoolean lock = new AtomicBoolean(false);
private int maxElementsPerNode;
private QuadTree<T> tree;
public QuadNode(float minX, float minY, float maxX, float maxY, int maxElementsPerNode, QuadTree<T> tree) {
this.bounds = new Box(minX, minY, maxX, maxY);
setLeafs(new HashSet<QuadLeaf<T>>(maxElementsPerNode));
setMaxElementsPerNode(maxElementsPerNode);
setTree(tree);
}
public boolean put(QuadLeaf<T> leaf) {
QuadNode<T> node = getChild(leaf.x, leaf.y);
if (hasChildren.get() && node != null) return node.put(leaf);
if(contains(leaf.value))
return false;
while(!lock()) {
// spinlock
}
try {
if (leafs.size() < maxElementsPerNode && leafs.add(leaf)) {
leaf.node = this;
return true;
} else if(leafs.contains(leaf))
return false;
/*if (this.leaf.x == leaf.x && this.leaf.y == leaf.y) {
boolean changed = false;
for (T value : leaf.values) {
if (!this.leaf.values.contains(value)) {
changed = this.leaf.values.add(value) || changed;
}
}
return changed;
}*/
this.divide();
return getChild(leaf.x, leaf.y).put(leaf);
} finally {
unlock();
}
}
public boolean put(float x, float y, T value) {
return put(new QuadLeaf<T>(x, y, value, this));
}
public boolean remove(float x, float y, T value) {
QuadNode<T> node = getChild(x, y);
if (hasChildren.get() && value != null && node != null) return node.remove(x, y, value);
while(!lock()) {
}
try {
QuadLeaf<T> leaf = getLeafByValue(value);
if (value != null && leaf != null) {
return leafs.remove(leaf);
}
} finally {
unlock();
}
return false;
}
public QuadLeaf<T> getLeafByValue(T value) {
return leafs.stream().filter(l -> l.value == value).findFirst().orElse(null);
}
public boolean update(float x, float y, T value) {
QuadNode<T> node = getChild(x, y);
if (hasChildren.get() && value != null && node != null) return node.update(x, y, value);
boolean removed = false;
while(!lock()) {
}
try {
QuadLeaf<T> leaf = getLeafByValue(value);
if(leaf == null)
return false;
// if we're still in this node just change the leafs position
if(bounds.contains(x, y)) {
leaf.x = x;
leaf.y = y;
return true;
// else we need to remove the leaf from this node and do a new search for the position from the root node
} else {
removed = true;
leafs.remove(leaf);
}
} finally {
unlock();
}
if(removed) {
return tree.put(x, y, value);
}
return false;
}
public Box getBounds() {
return this.bounds;
}
public void clear() {
if (hasChildren.get()) {
this.NW.clear();
this.NE.clear();
this.SE.clear();
this.SW.clear();
this.NW = null;
this.NE = null;
this.SE = null;
this.SW = null;
this.hasChildren.set(false);
} else {
this.leafs.clear();
}
}
public T get(float x, float y, AbstractFloat bestDistance) {
if (hasChildren.get()) {
T closest = null;
QuadNode<T> bestChild = this.getChild(x, y);
if (bestChild != null) {
closest = bestChild.get(x, y, bestDistance);
}
if (bestChild != this.NW && this.NW.bounds.calcDist(x, y) < bestDistance.value) {
T value = this.NW.get(x, y, bestDistance);
if (value != null) { closest = value; }
}
if (bestChild != this.NE && this.NE.bounds.calcDist(x, y) < bestDistance.value) {
T value = this.NE.get(x, y, bestDistance);
if (value != null) { closest = value; }
}
if (bestChild != this.SE && this.SE.bounds.calcDist(x, y) < bestDistance.value) {
T value = this.SE.get(x, y, bestDistance);
if (value != null) { closest = value; }
}
if (bestChild != this.SW && this.SW.bounds.calcDist(x, y) < bestDistance.value) {
T value = this.SW.get(x, y, bestDistance);
if (value != null) { closest = value; }
}
return closest;
}
if (leafs.size() > 0) {
while(!lock()) {
}
try {
T bestValue = null;
for(QuadLeaf<T> leaf : leafs) {
T value = leaf.value;
float distance = (float) Math.sqrt(
(leaf.x - x) * (leaf.x - x)
+ (leaf.y - y) * (leaf.y - y));
if (distance < bestDistance.value) {
bestDistance.value = distance;
bestValue = value;
}
}
return bestValue;
} finally {
unlock();
}
}
return null;
}
public ArrayList<T> get(float x, float y, float maxDistance, ArrayList<T> values) {
if (hasChildren.get()) {
if (this.NW.bounds.calcDist(x, y) <= maxDistance) {
this.NW.get(x, y, maxDistance, values);
}
if (this.NE.bounds.calcDist(x, y) <= maxDistance) {
this.NE.get(x, y, maxDistance, values);
}
if (this.SE.bounds.calcDist(x, y) <= maxDistance) {
this.SE.get(x, y, maxDistance, values);
}
if (this.SW.bounds.calcDist(x, y) <= maxDistance) {
this.SW.get(x, y, maxDistance, values);
}
return values;
}
if (this.leafs.size() > 0) {
while(!lock()) {
}
try {
for(QuadLeaf<T> leaf : leafs) {
float distance = (leaf.x - x) * (leaf.x - x) + (leaf.y - y) * (leaf.y - y);
if (distance <= maxDistance * maxDistance) {
values.add(leaf.value);
}
}
} finally {
unlock();
}
}
return values;
}
/*public ArrayList<T> get(Box bounds, ArrayList<T> values) {
if (this.hasChildren) {
if (this.NW.bounds.intersects(bounds)) {
this.NW.get(bounds, values);
}
if (this.NE.bounds.intersects(bounds)) {
this.NE.get(bounds, values);
}
if (this.SE.bounds.intersects(bounds)) {
this.SE.get(bounds, values);
}
if (this.SW.bounds.intersects(bounds)) {
this.SW.get(bounds, values);
}
return values;
}
if (this.leaf != null && this.leaf.values.size() > 0 && bounds.contains(this.leaf.x, this.leaf.y)) {
values.addAll(this.leaf.values);
}
return values;
}*/
/*public int execute(Box globalBounds, QuadTree.Executor<T> executor) {
int count = 0;
if (this.hasChildren) {
if (this.NW.bounds.intersects(globalBounds)) {
count += this.NW.execute(globalBounds, executor);
}
if (this.NE.bounds.intersects(globalBounds)) {
count += this.NE.execute(globalBounds, executor);
}
if (this.SE.bounds.intersects(globalBounds)) {
count += this.SE.execute(globalBounds, executor);
}
if (this.SW.bounds.intersects(globalBounds)) {
count += this.SW.execute(globalBounds, executor);
}
return count;
}
if (this.leaf != null && this.leaf.values.size() > 0 && globalBounds.contains(this.leaf.x, this.leaf.y)) {
count += this.leaf.values.size();
for (T object : this.leaf.values) executor.execute(this.leaf.x, this.leaf.y, object);
}
return count;
}*/
private void divide() {
hasChildren.compareAndSet(false, true);
this.NW = new QuadNode<T>(this.bounds.minX, this.bounds.centreY, this.bounds.centreX, this.bounds.maxY, maxElementsPerNode, tree);
this.NE = new QuadNode<T>(this.bounds.centreX, this.bounds.centreY, this.bounds.maxX, this.bounds.maxY, maxElementsPerNode, tree);
this.SE = new QuadNode<T>(this.bounds.centreX, this.bounds.minY, this.bounds.maxX, this.bounds.centreY, maxElementsPerNode, tree);
this.SW = new QuadNode<T>(this.bounds.minX, this.bounds.minY, this.bounds.centreX, this.bounds.centreY, maxElementsPerNode, tree);
if (this.leafs.size() > 0) {
for(QuadLeaf<T> leaf : leafs)
getChild(leaf.x, leaf.y).put(leaf);
leafs.clear();
}
}
private QuadNode<T> getChild(float x, float y) {
if (hasChildren.get()) {
if (x < this.bounds.centreX) {
if (y < this.bounds.centreY)
return this.SW;
return this.NW;
}
if (y < this.bounds.centreY)
return this.SE;
return this.NE;
}
return null;
}
public QuadLeaf<T> firstLeaf() {
if (hasChildren.get()) {
QuadLeaf<T> leaf = this.SW.firstLeaf();
if (leaf == null) { leaf = this.NW.firstLeaf(); }
if (leaf == null) { leaf = this.SE.firstLeaf(); }
if (leaf == null) { leaf = this.NE.firstLeaf(); }
return leaf;
}
QuadLeaf<T> leaf = null;
while(!lock()) {
}
try {
for(QuadLeaf<T> leaf2 : leafs) {
leaf = leaf2;
break;
}
} finally {
unlock();
}
return leaf;
}
public boolean nextLeaf(QuadLeaf<T> currentLeaf, AbstractLeaf<T> nextLeaf) {
if (hasChildren.get()) {
boolean found = false;
if (currentLeaf.x <= this.bounds.centreX && currentLeaf.y <= this.bounds.centreY) {
found = this.SW.nextLeaf(currentLeaf, nextLeaf);
if (found) {
if (nextLeaf.value == null) { nextLeaf.value = this.NW.firstLeaf(); }
if (nextLeaf.value == null) { nextLeaf.value = this.SE.firstLeaf(); }
if (nextLeaf.value == null) { nextLeaf.value = this.NE.firstLeaf(); }
return true;
}
}
if (currentLeaf.x <= this.bounds.centreX && currentLeaf.y >= this.bounds.centreY) {
found = this.NW.nextLeaf(currentLeaf, nextLeaf);
if (found) {
if (nextLeaf.value == null) { nextLeaf.value = this.SE.firstLeaf(); }
if (nextLeaf.value == null) { nextLeaf.value = this.NE.firstLeaf(); }
return true;
}
}
if (currentLeaf.x >= this.bounds.centreX && currentLeaf.y <= this.bounds.centreY) {
found = this.SE.nextLeaf(currentLeaf, nextLeaf);
if (found) {
if (nextLeaf.value == null) { nextLeaf.value = this.NE.firstLeaf(); }
return true;
}
}
if (currentLeaf.x >= this.bounds.centreX && currentLeaf.y >= this.bounds.centreY) {
return this.NE.nextLeaf(currentLeaf, nextLeaf);
}
return false;
}
return leafs.contains(currentLeaf);
}
public QuadLeaf<T> nextLeaf(QuadLeaf<T> currentLeaf) {
AbstractLeaf<T> nextLeaf = new AbstractLeaf<T>(null);
nextLeaf(currentLeaf, nextLeaf);
return nextLeaf.value;
}
public boolean lock() {
return lock.compareAndSet(false, true);
}
public boolean unlock() { return lock.compareAndSet(true, false); }
public boolean contains(T value) {
if(hasChildren.get()) {
if(NW != null && NW.contains(value))
return true;
if(NE != null && NE.contains(value))
return true;
if(SW != null && SW.contains(value))
return true;
if(SE != null && SE.contains(value))
return true;
return false;
} else {
while(!lock()) {
}
try {
for(QuadLeaf<T> leaf : leafs) {
if(leaf.value == value)
return true;
}
} finally {
unlock();
}
return false;
}
}
public Set<QuadLeaf<T>> getLeafs() {
return leafs;
}
public void setLeafs(Set<QuadLeaf<T>> leafs) {
this.leafs = leafs;
}
public int getMaxElementsPerNode() {
return maxElementsPerNode;
}
public void setMaxElementsPerNode(int maxElementsPerNode) {
this.maxElementsPerNode = maxElementsPerNode;
}
public QuadTree<T> getTree() {
return tree;
}
public void setTree(QuadTree<T> tree) {
this.tree = tree;
}
}