using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using TransportGame.Model; using TransportGame.Primitives; namespace TransportGame.Utils { public class QuadTree: ICollection where T : IPositionable { private const int Capacity = 8; /// /// Gets the subtree in northwest position /// public QuadTree NorthWest { get; private set; } /// /// Gets the subtree in northeast position /// public QuadTree NorthEast { get; private set; } /// /// Gets the subtree in southeast position /// public QuadTree SouthEast { get; private set; } /// /// Gets the subtree in southwest position /// public QuadTree SouthWest { get; private set; } /// /// Gets the boundary of this quad tree /// public Rectangle Boundary { get; private set; } private List points = new List(Capacity); /// /// Initializes a quad tree using specified boundaries /// /// Left /// Top /// Right /// Bottom public QuadTree(float left, float top, float right, float bottom) { Boundary = new Rectangle(left, top, right, bottom); } /// /// Initializes a quad tree using specified boundaries /// /// Boundaries public QuadTree(Rectangle boundary) { Boundary = boundary; } private void Subdivide() { float midx = Boundary.Left + Boundary.Width / 2f; float midy = Boundary.Bottom + Boundary.Height / 2f; NorthWest = new QuadTree(Boundary.Left, Boundary.Bottom, midx, midy); NorthEast = new QuadTree(midx, Boundary.Bottom, Boundary.Right, midy); SouthEast = new QuadTree(midx, midy, Boundary.Right, Boundary.Top); SouthWest = new QuadTree(Boundary.Left, midy, midx, Boundary.Top); foreach (var point in points) Add(point); points.Clear(); } private void Merge() { foreach (var point in NorthWest) points.Add(point); foreach (var point in NorthEast) points.Add(point); foreach (var point in SouthEast) points.Add(point); foreach (var point in SouthWest) points.Add(point); NorthWest = null; NorthEast = null; SouthEast = null; SouthWest = null; } /// /// Adds a point in this quad tree /// /// Point public void Add(T item) { // Precondition - point must be inside boundaries if (!Boundary.Contains(item.Position)) throw new ArgumentException("Point must be inside boundaries."); // Reached capacity, subdivide if (NorthWest == null && points.Count >= Capacity) Subdivide(); // Not divided in subtrees if (NorthWest == null) { if (!points.Any(p => p.Position.Equals(item.Position))) points.Add(item); } // Add in the right subtree else { if (NorthWest.Boundary.Contains(item.Position)) NorthWest.Add(item); else if (NorthEast.Boundary.Contains(item.Position)) NorthEast.Add(item); else if (SouthEast.Boundary.Contains(item.Position)) SouthEast.Add(item); else SouthWest.Add(item); } } /// /// Empties the entire tree /// public void Clear() { NorthWest = null; NorthEast = null; SouthEast = null; SouthWest = null; points.Clear(); } /// /// Tests if specified point is contained in this tree /// /// Point /// True if point is contained public bool Contains(T item) { if (NorthWest == null) return points.Contains(item); else return NorthWest.Contains(item) || NorthEast.Contains(item) || SouthEast.Contains(item) || SouthWest.Contains(item); } /// /// Copies all points to specified array /// /// Array /// Index where to start copying public void CopyTo(T[] array, int arrayIndex) { foreach (var point in this) array[arrayIndex++] = point; } /// /// Gets the number of points in this quad tree /// public int Count { get { return (NorthWest == null) ? points.Count : NorthWest.Count + NorthEast.Count + SouthEast.Count + SouthWest.Count; } } /// /// Returns true if this container is read-only. /// public bool IsReadOnly { get { return false; } } /// /// Removes an item from the tree /// /// Item to remove /// True if item was removed public bool Remove(T item) { if (NorthWest == null) return points.Remove(item); else { bool result = NorthWest.Remove(item) || NorthEast.Remove(item) || SouthEast.Remove(item) || SouthWest.Remove(item); // We can merge subdivisions if (Count < (Capacity - Capacity / 3)) Merge(); return result; } } /// /// Gets an enumerator for this collection /// /// System.Collections.IEnumerator IEnumerable.GetEnumerator() { foreach (var point in this) yield return point; } /// /// Gets enumerator for this collection /// /// public IEnumerator GetEnumerator() { if (NorthWest == null) { foreach (var point in points) yield return point; } else { foreach (var point in NorthWest) yield return point; foreach (var point in NorthEast) yield return point; foreach (var point in SouthEast) yield return point; foreach (var point in SouthWest) yield return point; } } /// /// Gets all the points in the specified region /// /// Region /// Points public IEnumerable Query(Rectangle rect) { // No intersection if (!Rectangle.Intersect(rect, Boundary)) return Enumerable.Empty(); if (NorthWest == null) { return points.Where(p => rect.Contains(p.Position)); } else { return NorthWest.Query(rect) .Concat(NorthEast.Query(rect)) .Concat(SouthEast.Query(rect)) .Concat(SouthWest.Query(rect)); } } /// /// Gets all the points in the specified region /// /// Left /// Top /// Right /// Bottom /// Points public IEnumerable Query(float left, float top, float right, float bottom) { return Query(new Rectangle(left, top, right, bottom)); } } }