/Engine/Core/QuadTree.cs

# · C# · 601 lines · 385 code · 87 blank · 129 comment · 56 complexity · c656e6c8285ab4ac7062767a5a540c47 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using Microsoft.Xna.Framework;
  5. using Engine;
  6. using Engine.Core;
  7. namespace Engine.Core
  8. {
  9. /// <summary>
  10. /// A position item in a quadtree
  11. /// </summary>
  12. /// <typeparam name="T">The type of the QuadTree item's parent</typeparam>
  13. public class QuadTreePositionItem<T>
  14. {
  15. public delegate void MoveHandler(QuadTreePositionItem<T> positionItem);
  16. public delegate void DestroyHandler(QuadTreePositionItem<T> positionItem);
  17. public event MoveHandler Move;
  18. public event DestroyHandler Destroy;
  19. protected void OnMove()
  20. {
  21. // Update rectangles
  22. rect.TopLeft = position - (size * .5f);
  23. rect.BottomRight = position + (size * .5f);
  24. // Call event handler
  25. if (Move != null) Move(this);
  26. }
  27. protected void OnDestroy()
  28. {
  29. if (Destroy != null) Destroy(this);
  30. }
  31. private Vector2 position;
  32. public Vector2 Position
  33. {
  34. get { return position; }
  35. set
  36. {
  37. position = value;
  38. OnMove();
  39. }
  40. }
  41. private Vector2 size;
  42. public Vector2 Size
  43. {
  44. get { return size; }
  45. set
  46. {
  47. size = value;
  48. rect.TopLeft = position - (size / 2f);
  49. rect.BottomRight = position + (size / 2f);
  50. OnMove();
  51. }
  52. }
  53. private FRect rect;
  54. public FRect Rect
  55. {
  56. get { return rect; }
  57. }
  58. private T parent;
  59. public T Parent
  60. {
  61. get { return parent; }
  62. }
  63. public QuadTreePositionItem(T parent, Vector2 position, Vector2 size)
  64. {
  65. this.rect = new FRect(0f, 0f, 1f, 1f);
  66. this.parent = parent;
  67. this.position = position;
  68. this.size = size;
  69. OnMove();
  70. }
  71. public void Delete()
  72. {
  73. OnDestroy();
  74. }
  75. }
  76. public class QuadTreeNode<T>
  77. {
  78. public delegate void ResizeDelegate(FRect newSize);
  79. protected FRect rect;
  80. public FRect Rect
  81. {
  82. get { return rect; }
  83. protected set { rect = value; }
  84. }
  85. protected int MaxItems;
  86. protected bool IsPartitioned;
  87. protected QuadTreeNode<T> ParentNode;
  88. protected QuadTreeNode<T> TopLeftNode;
  89. protected QuadTreeNode<T> TopRightNode;
  90. protected QuadTreeNode<T> BottomLeftNode;
  91. protected QuadTreeNode<T> BottomRightNode;
  92. protected List<QuadTreePositionItem<T>> Items;
  93. protected ResizeDelegate WorldResize;
  94. public QuadTreeNode(QuadTreeNode<T> parentNode, FRect rect, int maxItems)
  95. {
  96. ParentNode = parentNode;
  97. Rect = rect;
  98. MaxItems = maxItems;
  99. IsPartitioned = false;
  100. Items = new List<QuadTreePositionItem<T>>();
  101. }
  102. public QuadTreeNode(FRect rect, int maxItems, ResizeDelegate worldResize)
  103. {
  104. ParentNode = null;
  105. Rect = rect;
  106. MaxItems = maxItems;
  107. WorldResize = worldResize;
  108. IsPartitioned = false;
  109. Items = new List<QuadTreePositionItem<T>>();
  110. }
  111. public void Insert(QuadTreePositionItem<T> item)
  112. {
  113. // If partitioned, try to find child node to add to
  114. if (!InsertInChild(item))
  115. {
  116. item.Destroy += new QuadTreePositionItem<T>.DestroyHandler(ItemDestroy);
  117. item.Move += new QuadTreePositionItem<T>.MoveHandler(ItemMove);
  118. Items.Add(item);
  119. // Check if this node needs to be partitioned
  120. if (!IsPartitioned && Items.Count >= MaxItems)
  121. {
  122. Partition();
  123. }
  124. }
  125. }
  126. protected bool InsertInChild(QuadTreePositionItem<T> item)
  127. {
  128. if (!IsPartitioned) return false;
  129. if (TopLeftNode.ContainsRect(item.Rect))
  130. TopLeftNode.Insert(item);
  131. else if (TopRightNode.ContainsRect(item.Rect))
  132. TopRightNode.Insert(item);
  133. else if (BottomLeftNode.ContainsRect(item.Rect))
  134. BottomLeftNode.Insert(item);
  135. else if (BottomRightNode.ContainsRect(item.Rect))
  136. BottomRightNode.Insert(item);
  137. else return false; // insert in child failed
  138. return true;
  139. }
  140. public bool PushItemDown(int i)
  141. {
  142. if (InsertInChild(Items[i]))
  143. {
  144. RemoveItem(i);
  145. return true;
  146. }
  147. else return false;
  148. }
  149. public void PushItemUp(int i)
  150. {
  151. QuadTreePositionItem<T> m = Items[i];
  152. RemoveItem(i);
  153. ParentNode.Insert(m);
  154. }
  155. protected void Partition()
  156. {
  157. // Create the nodes
  158. Vector2 MidPoint = Vector2.Divide(Vector2.Add(Rect.TopLeft, Rect.BottomRight), 2.0f);
  159. TopLeftNode = new QuadTreeNode<T>(this, new FRect(Rect.TopLeft, MidPoint), MaxItems);
  160. TopRightNode = new QuadTreeNode<T>(this, new FRect(new Vector2(MidPoint.X, Rect.Top), new Vector2(Rect.Right, MidPoint.Y)), MaxItems);
  161. BottomLeftNode = new QuadTreeNode<T>(this, new FRect(new Vector2(Rect.Left, MidPoint.Y), new Vector2(MidPoint.X, Rect.Bottom)), MaxItems);
  162. BottomRightNode = new QuadTreeNode<T>(this, new FRect(MidPoint, Rect.BottomRight), MaxItems);
  163. IsPartitioned = true;
  164. // Try to push items down to child nodes
  165. int i = 0;
  166. while (i < Items.Count)
  167. {
  168. if (!PushItemDown(i))
  169. {
  170. i++;
  171. }
  172. }
  173. }
  174. public void GetItems(Vector2 Point, ref List<QuadTreePositionItem<T>> ItemsFound)
  175. {
  176. // test the point against this node
  177. if (Rect.Contains(Point))
  178. {
  179. // test the point in each item
  180. foreach (QuadTreePositionItem<T> Item in Items)
  181. {
  182. if (Item.Rect.Contains(Point)) ItemsFound.Add(Item);
  183. }
  184. // query all subtrees
  185. if (IsPartitioned)
  186. {
  187. TopLeftNode.GetItems(Point, ref ItemsFound);
  188. TopRightNode.GetItems(Point, ref ItemsFound);
  189. BottomLeftNode.GetItems(Point, ref ItemsFound);
  190. BottomRightNode.GetItems(Point, ref ItemsFound);
  191. }
  192. }
  193. }
  194. /// <summary>
  195. /// Gets a list of items intersecting a specified rectangle
  196. /// </summary>
  197. /// <param name="Rect">The rectangle</param>
  198. /// <param name="ItemsFound">The list to add found items to (list will not be cleared first)</param>
  199. /// <remarks>ItemsFound is assumed to be initialized, and will not be cleared</remarks>
  200. public void GetItems(FRect Rect, ref List<QuadTreePositionItem<T>> ItemsFound)
  201. {
  202. // test the point against this node
  203. if (Rect.Intersects(this.rect))
  204. {
  205. // test the point in each item
  206. foreach (QuadTreePositionItem<T> Item in Items)
  207. {
  208. if (Item.Rect.Intersects(Rect))
  209. ItemsFound.Add(Item);
  210. }
  211. // query all subtrees
  212. if (IsPartitioned)
  213. {
  214. TopLeftNode.GetItems(Rect, ref ItemsFound);
  215. TopRightNode.GetItems(Rect, ref ItemsFound);
  216. BottomLeftNode.GetItems(Rect, ref ItemsFound);
  217. BottomRightNode.GetItems(Rect, ref ItemsFound);
  218. }
  219. }
  220. }
  221. /// <summary>
  222. /// Gets a list of all items within this node
  223. /// </summary>
  224. /// <param name="ItemsFound">The list to add found items to (list will not be cleared first)</param>
  225. /// <remarks>ItemsFound is assumed to be initialized, and will not be cleared</remarks>
  226. public void GetAllItems(ref List<QuadTreePositionItem<T>> ItemsFound)
  227. {
  228. ItemsFound.AddRange(Items);
  229. // query all subtrees
  230. if (IsPartitioned)
  231. {
  232. TopLeftNode.GetAllItems(ref ItemsFound);
  233. TopRightNode.GetAllItems(ref ItemsFound);
  234. BottomLeftNode.GetAllItems(ref ItemsFound);
  235. BottomRightNode.GetAllItems(ref ItemsFound);
  236. }
  237. }
  238. /// <summary>
  239. /// Finds the node containing a specified item
  240. /// </summary>
  241. /// <param name="Item">The item to find</param>
  242. /// <returns>The node containing the item</returns>
  243. public QuadTreeNode<T> FindItemNode(QuadTreePositionItem<T> Item)
  244. {
  245. if (Items.Contains(Item)) return this;
  246. else if (IsPartitioned)
  247. {
  248. QuadTreeNode<T> n = null;
  249. // Check the nodes that could contain the item
  250. if (TopLeftNode.ContainsRect(Item.Rect))
  251. {
  252. n = TopLeftNode.FindItemNode(Item);
  253. }
  254. if (n == null &&
  255. TopRightNode.ContainsRect(Item.Rect))
  256. {
  257. n = TopRightNode.FindItemNode(Item);
  258. }
  259. if (n == null &&
  260. BottomLeftNode.ContainsRect(Item.Rect))
  261. {
  262. n = BottomLeftNode.FindItemNode(Item);
  263. }
  264. if (n == null &&
  265. BottomRightNode.ContainsRect(Item.Rect))
  266. {
  267. n = BottomRightNode.FindItemNode(Item);
  268. }
  269. return n;
  270. }
  271. else return null;
  272. }
  273. /// <summary>
  274. /// Destroys this node
  275. /// </summary>
  276. public void Destroy()
  277. {
  278. // Destroy all child nodes
  279. if (IsPartitioned)
  280. {
  281. TopLeftNode.Destroy();
  282. TopRightNode.Destroy();
  283. BottomLeftNode.Destroy();
  284. BottomRightNode.Destroy();
  285. TopLeftNode = null;
  286. TopRightNode = null;
  287. BottomLeftNode = null;
  288. BottomRightNode = null;
  289. }
  290. // Remove all items
  291. while (Items.Count > 0)
  292. {
  293. RemoveItem(0);
  294. }
  295. }
  296. /// <summary>
  297. /// Removes an item from this node
  298. /// </summary>
  299. /// <param name="item">The item to remove</param>
  300. public void RemoveItem(QuadTreePositionItem<T> item)
  301. {
  302. // Find and remove the item
  303. if (Items.Contains(item))
  304. {
  305. item.Move -= new QuadTreePositionItem<T>.MoveHandler(ItemMove);
  306. item.Destroy -= new QuadTreePositionItem<T>.DestroyHandler(ItemDestroy);
  307. Items.Remove(item);
  308. }
  309. }
  310. /// <summary>
  311. /// Removes an item from this node at a specific index
  312. /// </summary>
  313. /// <param name="i">the index of the item to remove</param>
  314. protected void RemoveItem(int i)
  315. {
  316. if (i < Items.Count)
  317. {
  318. Items[i].Move -= new QuadTreePositionItem<T>.MoveHandler(ItemMove);
  319. Items[i].Destroy -= new QuadTreePositionItem<T>.DestroyHandler(ItemDestroy);
  320. Items.RemoveAt(i);
  321. }
  322. }
  323. /// <summary>
  324. /// Handles item movement
  325. /// </summary>
  326. /// <param name="item">The item that moved</param>
  327. public void ItemMove(QuadTreePositionItem<T> item)
  328. {
  329. // Find the item
  330. if (Items.Contains(item))
  331. {
  332. int i = Items.IndexOf(item);
  333. // Try to push the item down to the child
  334. if (!PushItemDown(i))
  335. {
  336. // otherwise, if not root, push up
  337. if (ParentNode != null)
  338. {
  339. PushItemUp(i);
  340. }
  341. else if (!ContainsRect(item.Rect))
  342. {
  343. WorldResize(new FRect(
  344. Vector2.Min(Rect.TopLeft, item.Rect.TopLeft) * 2,
  345. Vector2.Max(Rect.BottomRight, item.Rect.BottomRight) * 2));
  346. }
  347. }
  348. }
  349. else
  350. {
  351. // this node doesn't contain that item, stop notifying it about it
  352. item.Move -= new QuadTreePositionItem<T>.MoveHandler(ItemMove);
  353. }
  354. }
  355. /// <summary>
  356. /// Handles item destruction
  357. /// </summary>
  358. /// <param name="item">The item that is being destroyed</param>
  359. public void ItemDestroy(QuadTreePositionItem<T> item)
  360. {
  361. RemoveItem(item);
  362. }
  363. /// <summary>
  364. /// Tests whether this node contains a rectangle
  365. /// </summary>
  366. /// <param name="rect">The rectangle to test</param>
  367. /// <returns>Whether or not this node contains the specified rectangle</returns>
  368. public bool ContainsRect(FRect rect)
  369. {
  370. return (rect.TopLeft.X >= Rect.TopLeft.X &&
  371. rect.TopLeft.Y >= Rect.TopLeft.Y &&
  372. rect.BottomRight.X <= Rect.BottomRight.X &&
  373. rect.BottomRight.Y <= Rect.BottomRight.Y);
  374. }
  375. }
  376. /// <summary>
  377. /// A QuadTree for partitioning a space into rectangles
  378. /// </summary>
  379. /// <typeparam name="T">The type of the QuadTree's items' parents</typeparam>
  380. /// <remarks>This QuadTree automatically resizes as needed</remarks>
  381. public class QuadTree<T>
  382. {
  383. /// <summary>
  384. /// The head node of the QuadTree
  385. /// </summary>
  386. protected QuadTreeNode<T> rootNode;
  387. /// <summary>
  388. /// Gets the world rectangle
  389. /// </summary>
  390. public FRect WorldRect
  391. {
  392. get { return rootNode.Rect; }
  393. }
  394. /// <summary>
  395. /// The maximum number of items in any node before partitioning
  396. /// </summary>
  397. protected int maxItems;
  398. /// <summary>
  399. /// QuadTree constructor
  400. /// </summary>
  401. /// <param name="worldRect">The world rectangle for this QuadTree (a rectangle containing all items at all times)</param>
  402. /// <param name="maxItems">Maximum number of items in any cell of the QuadTree before partitioning</param>
  403. public QuadTree(FRect worldRect, int maxItems)
  404. {
  405. this.rootNode = new QuadTreeNode<T>(worldRect, maxItems, Resize);
  406. this.maxItems = maxItems;
  407. }
  408. /// <summary>
  409. /// QuadTree constructor
  410. /// </summary>
  411. /// <param name="size">The size of the QuadTree (i.e. the bottom-right with a top-left of (0,0))</param>
  412. /// <param name="maxItems">Maximum number of items in any cell of the QuadTree before partitioning</param>
  413. /// <remarks>This constructor is for ease of use</remarks>
  414. public QuadTree(Vector2 size, int maxItems)
  415. : this(new FRect(Vector2.Zero, size), maxItems)
  416. {
  417. // Nothing extra to initialize
  418. }
  419. /// <summary>
  420. /// Inserts an item into the QuadTree
  421. /// </summary>
  422. /// <param name="item">The item to insert</param>
  423. /// <remarks>Checks to see if the world needs resizing and does so if needed</remarks>
  424. public QuadTreePositionItem<T> Insert(QuadTreePositionItem<T> item)
  425. {
  426. // check if the world needs resizing
  427. if (!rootNode.ContainsRect(item.Rect))
  428. {
  429. Resize(new FRect(
  430. Vector2.Min(rootNode.Rect.TopLeft, item.Rect.TopLeft) * 2,
  431. Vector2.Max(rootNode.Rect.BottomRight, item.Rect.BottomRight) * 2));
  432. }
  433. rootNode.Insert(item);
  434. return item;
  435. }
  436. /// <summary>
  437. /// Inserts an item into the QuadTree
  438. /// </summary>
  439. /// <param name="parent">The parent of the new position item</param>
  440. /// <param name="position">The position of the new position item</param>
  441. /// <param name="size">The size of the new position item</param>
  442. /// <returns>A new position item</returns>
  443. /// <remarks>Checks to see if the world needs resizing and does so if needed</remarks>
  444. public QuadTreePositionItem<T> Insert(T parent, Vector2 position, Vector2 size)
  445. {
  446. QuadTreePositionItem<T> item = new QuadTreePositionItem<T>(parent, position, size);
  447. // check if the world needs resizing
  448. if (!rootNode.ContainsRect(item.Rect))
  449. {
  450. Resize(new FRect(
  451. Vector2.Min(rootNode.Rect.TopLeft, item.Rect.TopLeft) * 2,
  452. Vector2.Max(rootNode.Rect.BottomRight, item.Rect.BottomRight) * 2));
  453. }
  454. rootNode.Insert(item);
  455. return item;
  456. }
  457. /// <summary>
  458. /// Resizes the Quadtree field
  459. /// </summary>
  460. /// <param name="newWorld">The new field</param>
  461. /// <remarks>This is an expensive operation, so try to initialize the world to a big enough size</remarks>
  462. public void Resize(FRect newWorld)
  463. {
  464. // Get all of the items in the tree
  465. List<QuadTreePositionItem<T>> Components = new List<QuadTreePositionItem<T>>();
  466. GetAllItems(ref Components);
  467. // Destroy the head node
  468. rootNode.Destroy();
  469. rootNode = null;
  470. // Create a new head
  471. rootNode = new QuadTreeNode<T>(newWorld, maxItems, Resize);
  472. // Reinsert the items
  473. foreach (QuadTreePositionItem<T> m in Components)
  474. {
  475. rootNode.Insert(m);
  476. }
  477. }
  478. /// <summary>
  479. /// Gets a list of items containing a specified point
  480. /// </summary>
  481. /// <param name="Point">The point</param>
  482. /// <param name="ItemsFound">The list to add found items to (list will not be cleared first)</param>
  483. public void GetItems(Vector2 Point, ref List<QuadTreePositionItem<T>> ItemsList)
  484. {
  485. if (ItemsList != null)
  486. {
  487. rootNode.GetItems(Point, ref ItemsList);
  488. }
  489. }
  490. /// <summary>
  491. /// Gets a list of items intersecting a specified rectangle
  492. /// </summary>
  493. /// <param name="Rect">The rectangle</param>
  494. /// <param name="ItemsFound">The list to add found items to (list will not be cleared first)</param>
  495. public void GetItems(FRect Rect, ref List<QuadTreePositionItem<T>> ItemsList)
  496. {
  497. if (ItemsList != null)
  498. {
  499. rootNode.GetItems(Rect, ref ItemsList);
  500. }
  501. }
  502. /// <summary>
  503. /// Get a list of all items in the quadtree
  504. /// </summary>
  505. /// <param name="ItemsFound">The list to add found items to (list will not be cleared first)</param>
  506. public void GetAllItems(ref List<QuadTreePositionItem<T>> ItemsList)
  507. {
  508. if (ItemsList != null)
  509. {
  510. rootNode.GetAllItems(ref ItemsList);
  511. }
  512. }
  513. }
  514. }