using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
namespace Gaia
{
///
/// A region quadtree implementation used for fast lookup in a two dimensional world.
///
///
/// The type to store inside the tree.
///
///
/// This implementation is not thread-safe.
///
public class Quadtree
{
///
/// The maximum number of nodes per tree.
///
private readonly int nodeCapacity = 32;
///
/// The nodes inside this region.
///
private readonly List nodes;
///
/// The child trees inside this region.
///
private Quadtree[] children;
///
/// The boundaries of this region.
///
private Rect boundaries;
///
/// Gets the number of values inside this tree.
///
public int Count
{
get;
private set;
}
///
/// Initializes a new instance of the class.
///
///
/// The boundaries of the region.
///
///
/// The maximum number of nodes per tree.
/// If the amount of nodes exceeds the tree will be subdivided into 4 sub trees.
/// A value of 32 seems fine in terms of insert and remove speed.
/// A value greater than 32 improves insert speed but slows down remove speed.
///
public Quadtree(Rect boundaries, int nodeCapacity = 32)
{
this.boundaries = boundaries;
this.nodeCapacity = nodeCapacity;
this.nodes = new List(nodeCapacity);
}
///
/// Inserts a value into the region.
///
///
/// The X component of the value's position.
///
///
/// The y component of the value's position.
///
///
/// The value to insert.
///
///
/// true if the value was inserted into the region;
/// false if the value's position was outside the region.
///
public bool Insert(float x, float y, T value)
{
var position = new Vector2(x, y);
var node = new QuadtreeNode(position, value);
return this.Insert(node);
}
///
/// Inserts a value into the region.
///
///
/// The position of the value.
///
///
/// The value to insert.
///
///
/// true if the value was inserted into the region;
/// false if the value's position was outside the region.
///
public bool Insert(Vector2 position, T value)
{
var node = new QuadtreeNode(position, value);
return this.Insert(node);
}
///
/// Inserts a node into the region.
///
///
/// The node to insert.
///
///
/// true if the node was inserted into the region;
/// false if the position of the node was outside the region.
///
private bool Insert(QuadtreeNode node)
{
if (!this.boundaries.Contains(node.Position))
{
return false;
}
if (this.children != null)
{
Quadtree child;
if (node.Position.y < this.children[2].boundaries.yMin)
{
if (node.Position.x < this.children[1].boundaries.xMin)
{
child = this.children[0];
}
else
{
child = this.children[1];
}
}
else
{
if (node.Position.x < this.children[1].boundaries.xMin)
{
child = this.children[2];
}
else
{
child = this.children[3];
}
}
if (child.Insert(node))
{
this.Count++;
return true;
}
}
if (this.nodes.Count < this.nodeCapacity)
{
this.nodes.Add(node);
this.Count++;
return true;
}
this.Subdivide();
return this.Insert(node);
}
///
/// Returns the values that are within the specified .
///
///
/// A rectangle representing the region to query.
///
///
/// Any value found inside the specified .
///
public IEnumerable Find(Rect range)
{
if (this.Count == 0)
{
yield break;
}
var rangexMax = range.xMax;
var rangexMin = range.xMin;
var rangeyMax = range.yMax;
var rangeyMin = range.yMin;
if (!(
this.boundaries.xMin < rangexMax &&
this.boundaries.xMax > rangexMin &&
this.boundaries.yMin < rangeyMax &&
this.boundaries.yMax > rangeyMin
))
{
yield break;
}
if (this.children == null)
{
for (var index = 0; index < this.nodes.Count; index++)
{
var node = this.nodes[index];
if (
node.Position.x >= rangexMin &&
node.Position.x <= rangexMax &&
node.Position.y >= rangeyMin &&
node.Position.y <= rangeyMax
)
{
yield return node.Value;
}
}
}
else
{
for (var index = 0; index < this.children.Length; index++)
{
var child = this.children[index];
foreach (var value in child.Find(range))
{
yield return value;
}
}
}
}
///
/// Removes a value from the region.
///
///
/// The X component of the value's position.
///
///
/// The Z component of the value's position.
///
///
/// The value to remove.
///
///
/// true if the value was removed from the region;
/// false if the value's position was outside the region.
///
public bool Remove(float x, float z, T value)
{
return this.Remove(new Vector2(x, z), value);
}
///
/// Removes a value from the region.
///
///
/// The position of the value.
///
///
/// The value to remove.
///
///
/// true if the value was removed from the region;
/// false if the value's position was outside the region.
///
public bool Remove(Vector2 position, T value)
{
if (this.Count == 0)
{
return false;
}
if (!this.boundaries.Contains(position))
{
return false;
}
if (this.children != null)
{
var isRemoved = false;
Quadtree child;
if (position.y < this.children[2].boundaries.yMin)
{
if (position.x < this.children[1].boundaries.xMin)
{
child = this.children[0];
}
else
{
child = this.children[1];
}
}
else
{
if (position.x < this.children[1].boundaries.xMin)
{
child = this.children[2];
}
else
{
child = this.children[3];
}
}
if (child.Remove(position, value))
{
isRemoved = true;
this.Count--;
}
if (this.Count <= this.nodeCapacity)
{
this.Combine();
}
return isRemoved;
}
for (var index = 0; index < this.nodes.Count; index++)
{
var node = this.nodes[index];
if (node.Position.Equals(position))
{
this.nodes.RemoveAt(index);
this.Count--;
return true;
}
}
return false;
}
///
/// Splits the region into 4 new subregions and moves the existing values into the new subregions.
///
private void Subdivide()
{
this.children = new Quadtree[4];
var width = this.boundaries.width * 0.5f;
var height = this.boundaries.height * 0.5f;
for (var index = 0; index < this.children.Length; index++)
{
var boundaries = new Rect(
this.boundaries.xMin + width * (index % 2),
this.boundaries.yMin + height * (index / 2),
width,
height
);
this.children[index] = new Quadtree(boundaries);
}
this.Count = 0;
for (var index = 0; index < this.nodes.Count; index++)
{
var node = this.nodes[index];
this.Insert(node);
}
this.nodes.Clear();
}
///
/// Joins the contents of the children into this region and remove the child regions.
///
private void Combine()
{
for (var index = 0; index < this.children.Length; index++)
{
var child = this.children[index];
this.nodes.AddRange(child.nodes);
}
this.children = null;
}
///
/// A single node inside a quadtree used for keeping values and their position.
///
private class QuadtreeNode
{
///
/// Gets the position of the value.
///
public Vector2 Position
{
get;
private set;
}
///
/// Gets the value.
///
public T Value
{
get;
private set;
}
///
/// Initializes a new instance of the class.
///
///
/// The position of the value.
///
///
/// The value.
///
public QuadtreeNode(Vector2 position, T value)
{
this.Position = position;
this.Value = value;
}
}
}
}