Quadtree.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. #if DUNGEN_QUADTREE
  2. using System;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. namespace DunGen.Collision
  6. {
  7. /// <summary>
  8. /// A generic quadtree that supports insertion, removal, querying, and automatic expansion.
  9. /// </summary>
  10. /// <typeparam name="T">The type of objects stored in the quadtree.</typeparam>
  11. public class Quadtree<T>
  12. {
  13. /// <summary>
  14. /// Internal node class that holds objects and may subdivide into four children.
  15. /// </summary>
  16. private class Node
  17. {
  18. public Bounds bounds;
  19. public List<T> objects;
  20. public Node[] children; // null if leaf
  21. /// <summary>
  22. /// True if this node has not been subdivided.
  23. /// </summary>
  24. public bool IsLeaf => children == null;
  25. public Node(Bounds bounds)
  26. {
  27. this.bounds = bounds;
  28. this.objects = new List<T>();
  29. }
  30. /// <summary>
  31. /// Subdivides this node into four children along the X and Z axes.
  32. /// Assumes a 2D quadtree (ignoring Y, but preserving its height).
  33. /// </summary>
  34. public void Subdivide()
  35. {
  36. children = new Node[4];
  37. // Each child has half the size in x and z, but retains the parent's height.
  38. Vector3 childSize = new Vector3(bounds.size.x / 2f, bounds.size.y, bounds.size.z / 2f);
  39. Vector3 center = bounds.center;
  40. float offsetX = childSize.x / 2f;
  41. float offsetZ = childSize.z / 2f;
  42. // Bottom-left quadrant (assuming z increases upward)
  43. children[0] = new Node(new Bounds(center + new Vector3(-offsetX, 0, -offsetZ), childSize));
  44. // Bottom-right quadrant
  45. children[1] = new Node(new Bounds(center + new Vector3(offsetX, 0, -offsetZ), childSize));
  46. // Top-left quadrant
  47. children[2] = new Node(new Bounds(center + new Vector3(-offsetX, 0, offsetZ), childSize));
  48. // Top-right quadrant
  49. children[3] = new Node(new Bounds(center + new Vector3(offsetX, 0, offsetZ), childSize));
  50. }
  51. }
  52. // The current quadtree root.
  53. private Node root;
  54. // Delegate to obtain a Bounds from an object of type T.
  55. private readonly Func<T, Bounds> getBounds;
  56. // Maximum objects a node can hold before subdividing.
  57. private readonly int maxObjectsPerNode;
  58. // Maximum allowed depth (to prevent infinite subdivision).
  59. private readonly int maxDepth;
  60. /// <summary>
  61. /// Constructs a quadtree with an initial bounding region.
  62. /// </summary>
  63. /// <param name="initialBounds">The starting bounds of the quadtree.</param>
  64. /// <param name="getBounds">A delegate to extract the Unity Bounds from a T object.</param>
  65. /// <param name="maxObjectsPerNode">Maximum objects per node before subdivision.</param>
  66. /// <param name="maxDepth">Maximum subdivision depth.</param>
  67. public Quadtree(Bounds initialBounds, Func<T, Bounds> getBounds, int maxObjectsPerNode = 4, int maxDepth = 10)
  68. {
  69. root = new Node(initialBounds);
  70. this.getBounds = getBounds;
  71. this.maxObjectsPerNode = maxObjectsPerNode;
  72. this.maxDepth = maxDepth;
  73. }
  74. /// <summary>
  75. /// Inserts an object into the quadtree.
  76. /// Automatically expands the tree if the object does not fit in the current bounds.
  77. /// </summary>
  78. /// <param name="obj">The object to insert.</param>
  79. public void Insert(T obj)
  80. {
  81. Bounds objBounds = getBounds(obj);
  82. // Expand the tree until the new object fits entirely within the root.
  83. while (!root.bounds.Contains(objBounds.min) || !root.bounds.Contains(objBounds.max))
  84. {
  85. ExpandRoot(objBounds);
  86. }
  87. Insert(root, obj, 0);
  88. }
  89. /// <summary>
  90. /// Internal recursive insertion method.
  91. /// </summary>
  92. private void Insert(Node node, T obj, int depth)
  93. {
  94. Bounds objBounds = getBounds(obj);
  95. // If not a leaf, try to delegate insertion into one of the children.
  96. if (!node.IsLeaf)
  97. {
  98. foreach (Node child in node.children)
  99. {
  100. // Insert only if the child completely contains the object's bounds.
  101. if (child.bounds.Contains(objBounds.min) && child.bounds.Contains(objBounds.max))
  102. {
  103. Insert(child, obj, depth + 1);
  104. return;
  105. }
  106. }
  107. }
  108. // Otherwise, store the object in this node.
  109. node.objects.Add(obj);
  110. // Subdivide this node if too many objects have accumulated and if we haven't reached maximum depth.
  111. if (node.IsLeaf && node.objects.Count > maxObjectsPerNode && depth < maxDepth)
  112. {
  113. node.Subdivide();
  114. // Reinsert existing objects into children if they fully fit; otherwise they remain in this node.
  115. for (int i = node.objects.Count - 1; i >= 0; i--)
  116. {
  117. T existingObj = node.objects[i];
  118. Bounds existingBounds = getBounds(existingObj);
  119. foreach (Node child in node.children)
  120. {
  121. if (child.bounds.Contains(existingBounds.min) && child.bounds.Contains(existingBounds.max))
  122. {
  123. Insert(child, existingObj, depth + 1);
  124. node.objects.RemoveAt(i);
  125. break;
  126. }
  127. }
  128. }
  129. }
  130. }
  131. /// <summary>
  132. /// Removes an object from the quadtree.
  133. /// </summary>
  134. /// <param name="obj">The object to remove.</param>
  135. /// <returns>True if the object was removed; otherwise false.</returns>
  136. public bool Remove(T obj)
  137. {
  138. return Remove(root, obj);
  139. }
  140. /// <summary>
  141. /// Internal recursive removal method.
  142. /// </summary>
  143. private bool Remove(Node node, T obj)
  144. {
  145. bool removed = false;
  146. Bounds objBounds = getBounds(obj);
  147. // Check current node.
  148. if (node.objects.Remove(obj))
  149. {
  150. removed = true;
  151. }
  152. // If there are children, check any that might intersect with the object's bounds.
  153. if (!node.IsLeaf)
  154. {
  155. foreach (Node child in node.children)
  156. {
  157. // Only search children whose bounds intersect the object's bounds.
  158. if (child.bounds.Intersects(objBounds))
  159. {
  160. removed |= Remove(child, obj);
  161. }
  162. }
  163. }
  164. return removed;
  165. }
  166. /// <summary>
  167. /// Queries the quadtree for all objects whose bounds overlap the specified region.
  168. /// </summary>
  169. /// <param name="queryBounds">The bounds to query.</param>
  170. /// <returns>A list of objects overlapping the query bounds.</returns>
  171. public List<T> Query(Bounds queryBounds)
  172. {
  173. List<T> results = new List<T>();
  174. Query(root, queryBounds, results);
  175. return results;
  176. }
  177. public void Query(Bounds queryBounds, ref List<T> results)
  178. {
  179. Query(root, queryBounds, results);
  180. }
  181. /// <summary>
  182. /// Recursively collects objects overlapping the query bounds.
  183. /// </summary>
  184. private void Query(Node node, Bounds queryBounds, List<T> results)
  185. {
  186. if (!node.bounds.Intersects(queryBounds))
  187. {
  188. return;
  189. }
  190. // Add objects in this node that intersect the query bounds.
  191. foreach (T obj in node.objects)
  192. {
  193. if (getBounds(obj).Intersects(queryBounds))
  194. {
  195. results.Add(obj);
  196. }
  197. }
  198. // If the node is subdivided, query its children.
  199. if (!node.IsLeaf)
  200. {
  201. foreach (Node child in node.children)
  202. {
  203. Query(child, queryBounds, results);
  204. }
  205. }
  206. }
  207. /// <summary>
  208. /// Draws the quadtree nodes and the objects contained in each node using Unity's Debug.DrawLine API.
  209. /// </summary>
  210. /// <param name="duration">Duration (in seconds) for which the lines should persist.</param>
  211. public void DrawDebug(float duration = 0f)
  212. {
  213. DrawDebug(root, duration);
  214. }
  215. /// <summary>
  216. /// Recursively draws node boundaries and objects.
  217. /// </summary>
  218. private void DrawDebug(Node node, float duration)
  219. {
  220. // Draw this node's bounds (white).
  221. DrawBounds(node.bounds, Color.white, duration);
  222. // Draw each object's bounds within this node (green).
  223. foreach (T obj in node.objects)
  224. {
  225. Bounds objBounds = getBounds(obj);
  226. DrawBounds(objBounds, Color.green, duration);
  227. }
  228. // Recursively draw children nodes if they exist.
  229. if (!node.IsLeaf)
  230. {
  231. foreach (Node child in node.children)
  232. {
  233. DrawDebug(child, duration);
  234. }
  235. }
  236. }
  237. /// <summary>
  238. /// Helper method that draws a rectangle representing the bounds on the XZ plane.
  239. /// </summary>
  240. private void DrawBounds(Bounds b, Color color, float duration)
  241. {
  242. Vector3 center = b.center;
  243. Vector3 extents = b.extents;
  244. // Calculate corners (on the XZ plane; Y is kept at the center's Y).
  245. Vector3 topLeft = new Vector3(center.x - extents.x, center.y, center.z + extents.z);
  246. Vector3 topRight = new Vector3(center.x + extents.x, center.y, center.z + extents.z);
  247. Vector3 bottomRight = new Vector3(center.x + extents.x, center.y, center.z - extents.z);
  248. Vector3 bottomLeft = new Vector3(center.x - extents.x, center.y, center.z - extents.z);
  249. Debug.DrawLine(topLeft, topRight, color, duration);
  250. Debug.DrawLine(topRight, bottomRight, color, duration);
  251. Debug.DrawLine(bottomRight, bottomLeft, color, duration);
  252. Debug.DrawLine(bottomLeft, topLeft, color, duration);
  253. }
  254. /// <summary>
  255. /// Expands the root bounds so that it fully contains the specified object bounds.
  256. /// Instead of reinserting every object, the current root is wrapped in a new, larger root.
  257. /// This implementation uses a doubling strategy so that one child quadrant exactly matches the old root.
  258. /// </summary>
  259. private void ExpandRoot(Bounds objBounds)
  260. {
  261. // Save current root parameters.
  262. Vector3 oldCenter = root.bounds.center;
  263. Vector3 oldSize = root.bounds.size; // Must be nonzero and positive
  264. // Determine whether the new object is to the left/right and down/up of the current center.
  265. // If the new object is to the left, we want the old tree to be in the right quadrant, etc.
  266. bool newObjLeft = objBounds.center.x < oldCenter.x;
  267. bool newObjDown = objBounds.center.z < oldCenter.z;
  268. // Use a multiplier of 2 so that each child quadrant of the new root matches the old root exactly.
  269. Vector3 newSize = new Vector3(oldSize.x * 2f, oldSize.y, oldSize.z * 2f);
  270. // Determine offsets:
  271. // We want the child quadrant's center to equal oldCenter.
  272. // Since each child quadrant is half the new root's size,
  273. // oldCenter = newRoot.center + (dx * oldSize.x/2, 0, dz * oldSize.z/2)
  274. // Solve for newRoot.center:
  275. int dx = newObjLeft ? 1 : -1; // if new object is left, shift right so that old tree ends up in the opposite (right) quadrant
  276. int dz = newObjDown ? 1 : -1; // if new object is down, shift up so that old tree ends up in the opposite (up) quadrant
  277. Vector3 newRootCenter = oldCenter - new Vector3((dx * oldSize.x) / 2f, 0, (dz * oldSize.z) / 2f);
  278. // Create the new root with these computed parameters.
  279. Node newRoot = new Node(new Bounds(newRootCenter, newSize));
  280. newRoot.Subdivide();
  281. // Determine which child quadrant of the new root should exactly match the old root.
  282. // Child quadrant centers (relative to newRoot.center) are:
  283. // 0: bottom-left (-oldSize.x/2, -oldSize.z/2)
  284. // 1: bottom-right (oldSize.x/2, -oldSize.z/2)
  285. // 2: top-left (-oldSize.x/2, oldSize.z/2)
  286. // 3: top-right (oldSize.x/2, oldSize.z/2)
  287. int quadrant;
  288. if (dx == -1 && dz == -1)
  289. quadrant = 0; // new object is to right/up, so old tree goes bottom-left.
  290. else if (dx == 1 && dz == -1)
  291. quadrant = 1; // new object is to left/up, so old tree goes bottom-right.
  292. else if (dx == -1 && dz == 1)
  293. quadrant = 2; // new object is to right/down, so old tree goes top-left.
  294. else // (dx == 1 && dz == 1)
  295. quadrant = 3; // new object is to left/down, so old tree goes top-right.
  296. // Place the existing root into the appropriate quadrant.
  297. newRoot.children[quadrant] = root;
  298. root = newRoot;
  299. }
  300. /// <summary>
  301. /// Recursively collects all objects stored in the tree.
  302. /// </summary>
  303. private List<T> CollectAllObjects(Node node)
  304. {
  305. List<T> list = new List<T>(node.objects);
  306. if (!node.IsLeaf)
  307. {
  308. foreach (Node child in node.children)
  309. {
  310. list.AddRange(CollectAllObjects(child));
  311. }
  312. }
  313. return list;
  314. }
  315. }
  316. }
  317. #endif