using System; using System.Collections.Generic; namespace MonoGame.Extended.Collisions.QuadTree { /// /// Class for doing collision handling with a quad tree. /// public class QuadTree { /// /// The default maximum depth. /// public const int DefaultMaxDepth = 7; /// /// The default maximum objects per node. /// public const int DefaultMaxObjectsPerNode = 25; /// /// Contains the children of this node. /// protected List Children = new List(); /// /// Contains the data for this node in the quadtree. /// protected HashSet Contents = new HashSet(); /// /// Creates a quad tree with the given bounds. /// /// The bounds of the new quad tree. public QuadTree(RectangleF bounds) { CurrentDepth = 0; NodeBounds = bounds; } /// /// Gets or sets the current depth for this node in the quadtree. /// protected int CurrentDepth { get; set; } /// /// Gets or sets the maximum depth of the quadtree. /// protected int MaxDepth { get; set; } = DefaultMaxDepth; /// /// Gets or sets the maximum objects per node in this quadtree. /// protected int MaxObjectsPerNode { get; set; } = DefaultMaxObjectsPerNode; /// /// Gets the bounds of the area contained in this quad tree. /// public RectangleF NodeBounds { get; protected set; } /// /// Gets whether the current node is a leaf node. /// public bool IsLeaf => Children.Count == 0; /// /// Counts the number of unique targets in the current Quadtree. /// /// Returns the targets of objects found. public int NumTargets() { List dirtyItems = new List(); var objectCount = 0; // Do BFS on nodes to count children. var process = new Queue(); process.Enqueue(this); while (process.Count > 0) { var processing = process.Dequeue(); if (!processing.IsLeaf) { foreach (var child in processing.Children) { process.Enqueue(child); } } else { foreach (var data in processing.Contents) { if (data.Dirty == false) { objectCount++; data.MarkDirty(); dirtyItems.Add(data); } } } } foreach (var quadtreeData in dirtyItems) { quadtreeData.MarkClean(); } return objectCount; } /// /// Inserts the data into the tree. /// /// Data being inserted. public void Insert(QuadtreeData data) { var actorBounds = data.Bounds; // Object doesn't fit into this node. if (!NodeBounds.Intersects(actorBounds)) { return; } if (IsLeaf && Contents.Count >= MaxObjectsPerNode) { Split(); } if (IsLeaf) { AddToLeaf(data); } else { foreach (var child in Children) { child.Insert(data); } } } /// /// Removes data from the Quadtree /// /// The data to be removed. public void Remove(QuadtreeData data) { if (IsLeaf) { data.RemoveParent(this); Contents.Remove(data); } else { throw new InvalidOperationException($"Cannot remove from a non leaf {nameof(QuadTree)}"); } } /// /// Removes unnecessary leaf nodes and simplifies the quad tree. /// public void Shake() { if (IsLeaf) { return; } List dirtyItems = new List(); var numObjects = NumTargets(); if (numObjects == 0) { Children.Clear(); } else if (numObjects < MaxObjectsPerNode) { var process = new Queue(); process.Enqueue(this); while (process.Count > 0) { var processing = process.Dequeue(); if (!processing.IsLeaf) { foreach (var subTree in processing.Children) { process.Enqueue(subTree); } } else { foreach (var data in processing.Contents) { if (data.Dirty == false) { AddToLeaf(data); data.MarkDirty(); dirtyItems.Add(data); } } } } Children.Clear(); } foreach (var quadtreeData in dirtyItems) { quadtreeData.MarkClean(); } } private void AddToLeaf(QuadtreeData data) { data.AddParent(this); Contents.Add(data); } /// /// Splits a quadtree into quadrants. /// public void Split() { if (CurrentDepth + 1 >= MaxDepth) return; var min = NodeBounds.TopLeft; var max = NodeBounds.BottomRight; var center = NodeBounds.Center; RectangleF[] childAreas = { RectangleF.CreateFrom(min, center), RectangleF.CreateFrom(new Point2(center.X, min.Y), new Point2(max.X, center.Y)), RectangleF.CreateFrom(center, max), RectangleF.CreateFrom(new Point2(min.X, center.Y), new Point2(center.X, max.Y)) }; for (var i = 0; i < childAreas.Length; ++i) { var node = new QuadTree(childAreas[i]); Children.Add(node); Children[i].CurrentDepth = CurrentDepth + 1; } foreach (QuadtreeData contentQuadtree in Contents) { foreach (QuadTree childQuadtree in Children) { childQuadtree.Insert(contentQuadtree); } } Clear(); } /// /// Clear current node and all children /// public void ClearAll() { foreach (QuadTree childQuadtree in Children) childQuadtree.ClearAll(); Clear(); } private void Clear() { foreach (QuadtreeData quadtreeData in Contents) { quadtreeData.RemoveParent(this); } Contents.Clear(); } /// /// Queries the quadtree for targets that intersect with the given area. /// /// The area to query for overlapping targets /// A unique list of targets intersected by area. public List Query(ref RectangleF area) { var recursiveResult = new List(); QueryWithoutReset(ref area, recursiveResult); foreach (var quadtreeData in recursiveResult) { quadtreeData.MarkClean(); } return recursiveResult; } private void QueryWithoutReset(ref RectangleF area, List recursiveResult) { if (!NodeBounds.Intersects(area)) return; if (IsLeaf) { foreach (QuadtreeData quadtreeData in Contents) { if (quadtreeData.Dirty == false && quadtreeData.Bounds.Intersects(area)) { recursiveResult.Add(quadtreeData); quadtreeData.MarkDirty(); } } } else { for (int i = 0, size = Children.Count; i < size; i++) { Children[i].QueryWithoutReset(ref area, recursiveResult); } } } } }