QuadTree.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. using System;
  2. using System.Collections.Generic;
  3. using Microsoft.Xna.Framework;
  4. namespace MonoGame.Extended.Collisions.QuadTree
  5. {
  6. /// <summary>
  7. /// Class for doing collision handling with a quad tree.
  8. /// </summary>
  9. public class QuadTree
  10. {
  11. /// <summary>
  12. /// The default maximum depth.
  13. /// </summary>
  14. public const int DefaultMaxDepth = 7;
  15. /// <summary>
  16. /// The default maximum objects per node.
  17. /// </summary>
  18. public const int DefaultMaxObjectsPerNode = 25;
  19. /// <summary>
  20. /// Contains the children of this node.
  21. /// </summary>
  22. protected List<QuadTree> Children = new List<QuadTree>();
  23. /// <summary>
  24. /// Contains the data for this node in the quadtree.
  25. /// </summary>
  26. protected HashSet<QuadtreeData> Contents = new HashSet<QuadtreeData>();
  27. /// <summary>
  28. /// Creates a quad tree with the given bounds.
  29. /// </summary>
  30. /// <param name="bounds">The bounds of the new quad tree.</param>
  31. public QuadTree(RectangleF bounds)
  32. {
  33. CurrentDepth = 0;
  34. NodeBounds = bounds;
  35. }
  36. /// <summary>
  37. /// Gets or sets the current depth for this node in the quadtree.
  38. /// </summary>
  39. protected int CurrentDepth { get; set; }
  40. /// <summary>
  41. /// Gets or sets the maximum depth of the quadtree.
  42. /// </summary>
  43. protected int MaxDepth { get; set; } = DefaultMaxDepth;
  44. /// <summary>
  45. /// Gets or sets the maximum objects per node in this quadtree.
  46. /// </summary>
  47. protected int MaxObjectsPerNode { get; set; } = DefaultMaxObjectsPerNode;
  48. /// <summary>
  49. /// Gets the bounds of the area contained in this quad tree.
  50. /// </summary>
  51. public RectangleF NodeBounds { get; protected set; }
  52. /// <summary>
  53. /// Gets whether the current node is a leaf node.
  54. /// </summary>
  55. public bool IsLeaf => Children.Count == 0;
  56. /// <summary>
  57. /// Counts the number of unique targets in the current Quadtree.
  58. /// </summary>
  59. /// <returns>Returns the targets of objects found.</returns>
  60. public int NumTargets()
  61. {
  62. List<QuadtreeData> dirtyItems = new List<QuadtreeData>();
  63. var objectCount = 0;
  64. // Do BFS on nodes to count children.
  65. var process = new Queue<QuadTree>();
  66. process.Enqueue(this);
  67. while (process.Count > 0)
  68. {
  69. var processing = process.Dequeue();
  70. if (!processing.IsLeaf)
  71. {
  72. foreach (var child in processing.Children)
  73. {
  74. process.Enqueue(child);
  75. }
  76. }
  77. else
  78. {
  79. foreach (var data in processing.Contents)
  80. {
  81. if (data.Dirty == false)
  82. {
  83. objectCount++;
  84. data.MarkDirty();
  85. dirtyItems.Add(data);
  86. }
  87. }
  88. }
  89. }
  90. foreach (var quadtreeData in dirtyItems)
  91. {
  92. quadtreeData.MarkClean();
  93. }
  94. return objectCount;
  95. }
  96. /// <summary>
  97. /// Inserts the data into the tree.
  98. /// </summary>
  99. /// <param name="data">Data being inserted.</param>
  100. public void Insert(QuadtreeData data)
  101. {
  102. var actorBounds = data.Bounds;
  103. // Object doesn't fit into this node.
  104. if (!NodeBounds.Intersects(actorBounds))
  105. {
  106. return;
  107. }
  108. if (IsLeaf && Contents.Count >= MaxObjectsPerNode)
  109. {
  110. Split();
  111. }
  112. if (IsLeaf)
  113. {
  114. AddToLeaf(data);
  115. }
  116. else
  117. {
  118. foreach (var child in Children)
  119. {
  120. child.Insert(data);
  121. }
  122. }
  123. }
  124. /// <summary>
  125. /// Removes data from the Quadtree
  126. /// </summary>
  127. /// <param name="data">The data to be removed.</param>
  128. public void Remove(QuadtreeData data)
  129. {
  130. if (IsLeaf)
  131. {
  132. data.RemoveParent(this);
  133. Contents.Remove(data);
  134. }
  135. else
  136. {
  137. throw new InvalidOperationException($"Cannot remove from a non leaf {nameof(QuadTree)}");
  138. }
  139. }
  140. /// <summary>
  141. /// Removes unnecessary leaf nodes and simplifies the quad tree.
  142. /// </summary>
  143. public void Shake()
  144. {
  145. if (IsLeaf)
  146. {
  147. return;
  148. }
  149. List<QuadtreeData> dirtyItems = new List<QuadtreeData>();
  150. var numObjects = NumTargets();
  151. if (numObjects == 0)
  152. {
  153. Children.Clear();
  154. }
  155. else if (numObjects < MaxObjectsPerNode)
  156. {
  157. var process = new Queue<QuadTree>();
  158. process.Enqueue(this);
  159. while (process.Count > 0)
  160. {
  161. var processing = process.Dequeue();
  162. if (!processing.IsLeaf)
  163. {
  164. foreach (var subTree in processing.Children)
  165. {
  166. process.Enqueue(subTree);
  167. }
  168. }
  169. else
  170. {
  171. foreach (var data in processing.Contents)
  172. {
  173. if (data.Dirty == false)
  174. {
  175. AddToLeaf(data);
  176. data.MarkDirty();
  177. dirtyItems.Add(data);
  178. }
  179. }
  180. }
  181. }
  182. Children.Clear();
  183. }
  184. foreach (var quadtreeData in dirtyItems)
  185. {
  186. quadtreeData.MarkClean();
  187. }
  188. }
  189. private void AddToLeaf(QuadtreeData data)
  190. {
  191. data.AddParent(this);
  192. Contents.Add(data);
  193. }
  194. /// <summary>
  195. /// Splits a quadtree into quadrants.
  196. /// </summary>
  197. public void Split()
  198. {
  199. if (CurrentDepth + 1 >= MaxDepth) return;
  200. var min = NodeBounds.TopLeft;
  201. var max = NodeBounds.BottomRight;
  202. var center = NodeBounds.Center;
  203. RectangleF[] childAreas =
  204. {
  205. RectangleF.CreateFrom(min, center),
  206. RectangleF.CreateFrom(new Vector2(center.X, min.Y), new Vector2(max.X, center.Y)),
  207. RectangleF.CreateFrom(center, max),
  208. RectangleF.CreateFrom(new Vector2(min.X, center.Y), new Vector2(center.X, max.Y))
  209. };
  210. for (var i = 0; i < childAreas.Length; ++i)
  211. {
  212. var node = new QuadTree(childAreas[i]);
  213. Children.Add(node);
  214. Children[i].CurrentDepth = CurrentDepth + 1;
  215. }
  216. foreach (QuadtreeData contentQuadtree in Contents)
  217. {
  218. foreach (QuadTree childQuadtree in Children)
  219. {
  220. childQuadtree.Insert(contentQuadtree);
  221. }
  222. }
  223. Clear();
  224. }
  225. /// <summary>
  226. /// Clear current node and all children
  227. /// </summary>
  228. public void ClearAll()
  229. {
  230. foreach (QuadTree childQuadtree in Children)
  231. childQuadtree.ClearAll();
  232. Clear();
  233. }
  234. private void Clear()
  235. {
  236. foreach (QuadtreeData quadtreeData in Contents)
  237. {
  238. quadtreeData.RemoveParent(this);
  239. }
  240. Contents.Clear();
  241. }
  242. /// <summary>
  243. /// Queries the quadtree for targets that intersect with the given area.
  244. /// </summary>
  245. /// <param name="area">The area to query for overlapping targets</param>
  246. /// <returns>A unique list of targets intersected by area.</returns>
  247. public List<QuadtreeData> Query(ref RectangleF area)
  248. {
  249. var recursiveResult = new List<QuadtreeData>();
  250. QueryWithoutReset(ref area, recursiveResult);
  251. foreach (var quadtreeData in recursiveResult)
  252. {
  253. quadtreeData.MarkClean();
  254. }
  255. return recursiveResult;
  256. }
  257. private void QueryWithoutReset(ref RectangleF area, List<QuadtreeData> recursiveResult)
  258. {
  259. if (!NodeBounds.Intersects(area))
  260. return;
  261. if (IsLeaf)
  262. {
  263. foreach (QuadtreeData quadtreeData in Contents)
  264. {
  265. if (quadtreeData.Dirty == false && quadtreeData.Bounds.Intersects(area))
  266. {
  267. recursiveResult.Add(quadtreeData);
  268. quadtreeData.MarkDirty();
  269. }
  270. }
  271. }
  272. else
  273. {
  274. for (int i = 0, size = Children.Count; i < size; i++)
  275. {
  276. Children[i].QueryWithoutReset(ref area, recursiveResult);
  277. }
  278. }
  279. }
  280. }
  281. }