PageRenderTime 28ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/Engine/YnQuadTree.cs

http://yna.codeplex.com
C# | 267 lines | 167 code | 38 blank | 62 comment | 29 complexity | d5186ef4aa43ac5d568b187fcbc51f26 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. using Microsoft.Xna.Framework;
  2. using System.Collections.Generic;
  3. using Yna.Engine.Collision;
  4. namespace Yna.Engine
  5. {
  6. /// <summary>
  7. /// A QuadTree class for an optimised collision detection
  8. /// </summary>
  9. public class YnQuadTree
  10. {
  11. /// <summary>
  12. /// Gets or sets the number of object per node
  13. /// </summary>
  14. public int MaxObjectsPerNode
  15. {
  16. get { return _maxObjectsPerNode; }
  17. set
  18. {
  19. if (value > 0)
  20. _maxObjectsPerNode = value;
  21. }
  22. }
  23. /// <summary>
  24. /// Gets or sets the number of sub level
  25. /// </summary>
  26. public int MaxLevels
  27. {
  28. get { return _maxSubLevels; }
  29. set
  30. {
  31. if (value > 0)
  32. _maxSubLevels = value;
  33. }
  34. }
  35. private int _level;
  36. private int _maxObjectsPerNode;
  37. private int _maxSubLevels;
  38. private List<ICollidable2> _objects;
  39. private Rectangle _quadBounds;
  40. private YnQuadTree[] _nodes;
  41. #region delegates declarations
  42. public delegate void TestManyCallback(ICollidable2 colliderA, ICollidable2 colliderB);
  43. public delegate void TestOneCallback(ICollidable2 collidables);
  44. #endregion
  45. /// <summary>
  46. /// Create a new QuadTree
  47. /// </summary>
  48. /// <param name="level">Start level</param>
  49. /// <param name="quadBounds">The size to use for the QuadTree</param>
  50. public YnQuadTree(int level, Rectangle quadBounds)
  51. {
  52. _maxSubLevels = 5;
  53. _maxObjectsPerNode = 10;
  54. _level = level;
  55. _objects = new List<ICollidable2>();
  56. _quadBounds = quadBounds;
  57. _nodes = new YnQuadTree[4];
  58. for (int i = 0; i < 4; i++)
  59. _nodes[i] = null;
  60. }
  61. /// <summary>
  62. /// Clear the quadtree and add collidables objects on it
  63. /// </summary>
  64. /// <param name="collidables">An array of collidable objects</param>
  65. public void Begin(ICollidable2[] collidables)
  66. {
  67. Clear();
  68. foreach (ICollidable2 collidable in collidables)
  69. Add(collidable);
  70. }
  71. /// <summary>
  72. /// Gets all objects close to the collidable objects passed in parameter
  73. /// </summary>
  74. /// <param name="collidables"></param>
  75. /// <param name="functionTest"></param>
  76. /// <returns></returns>
  77. public bool TestCandidates(ICollidable2[] collidables, TestManyCallback testFunction)
  78. {
  79. bool hasCandidate = false;
  80. List<ICollidable2> candidates;
  81. foreach (ICollidable2 collidable in collidables)
  82. {
  83. candidates = GetCandidates(collidable);
  84. foreach (ICollidable2 candidate in candidates)
  85. {
  86. testFunction(collidable, candidate);
  87. hasCandidate = true;
  88. }
  89. }
  90. return hasCandidate;
  91. }
  92. public bool TestCandidates(ICollidable2 collidable, TestManyCallback functionTest)
  93. {
  94. return TestCandidates(new ICollidable2[] { collidable }, functionTest);
  95. }
  96. /// <summary>
  97. /// Gets all objects close to the collidable object passed in parameter
  98. /// </summary>
  99. /// <returns>True if objects are available to test otherwise return false</returns>
  100. /// <param name='colliderToTest'>The object to test with the collection</param>
  101. /// <param name='testFunction'>A callback function</param>
  102. public bool TestCandidates(ICollidable2 colliderToTest, TestOneCallback testFunction)
  103. {
  104. bool hasCandidate = false;
  105. List<ICollidable2> candidates = GetCandidates(colliderToTest);
  106. foreach (ICollidable2 candidate in candidates)
  107. {
  108. testFunction(candidate);
  109. hasCandidate = true;
  110. }
  111. return hasCandidate;
  112. }
  113. /// <summary>
  114. /// Get all that can potentially collide with this entity
  115. /// </summary>
  116. /// <param name="entity">A collidable entity to test</param>
  117. /// <returns>An array of collidable elements</returns>
  118. public List<ICollidable2> GetCandidates(ICollidable2 entity)
  119. {
  120. List<ICollidable2> candidates = new List<ICollidable2>();
  121. int index = GetNodeIndex(entity.Rectangle);
  122. // If the space is already splited we get node objects that can potentially collide with this entity
  123. if (index > -1 && _nodes[0] != null)
  124. candidates.AddRange(_nodes[index].GetCandidates(entity));
  125. // All remaining objects can potentially collide with this entity
  126. candidates.AddRange(_objects);
  127. return candidates;
  128. }
  129. /// <summary>
  130. /// Clear all nodes of the Quadtree
  131. /// </summary>
  132. public void Clear()
  133. {
  134. _objects.Clear();
  135. for (int i = 0; i < 4; i++)
  136. {
  137. if (_nodes[i] != null)
  138. {
  139. _nodes[i].Clear();
  140. _nodes[i] = null;
  141. }
  142. }
  143. }
  144. /// <summary>
  145. /// Split the Quadtree
  146. /// </summary>
  147. protected void Split()
  148. {
  149. int subWidth = _quadBounds.Width / 2;
  150. int subHeight = _quadBounds.Height / 2;
  151. /* -----------------------
  152. * | _node[1] | _node[0] |
  153. * |----------------------
  154. * | _node[2] | _node[3] |
  155. * -----------------------
  156. */
  157. _nodes[0] = new YnQuadTree(_level + 1, new Rectangle(_quadBounds.X + subWidth, _quadBounds.Y, subWidth, subHeight));
  158. _nodes[1] = new YnQuadTree(_level + 1, new Rectangle(_quadBounds.X, _quadBounds.Y, subWidth, subHeight));
  159. _nodes[2] = new YnQuadTree(_level + 1, new Rectangle(_quadBounds.X, _quadBounds.Y + subHeight, subWidth, subHeight));
  160. _nodes[3] = new YnQuadTree(_level + 1, new Rectangle(_quadBounds.X + subWidth, _quadBounds.Y + subHeight, subWidth, subHeight));
  161. }
  162. /// <summary>
  163. /// Gets the node index of the object.
  164. /// </summary>
  165. /// <param name="entityRectangle">Rectangle of the object</param>
  166. /// <returns>-1 if the object cannot completly fit whithin a child node then the node index between 0 and 3</returns>
  167. protected int GetNodeIndex(Rectangle entityRectangle)
  168. {
  169. int index = -1;
  170. float verticalMidPoint = _quadBounds.X + (_quadBounds.Width / 2);
  171. float horizontalMidPoint = _quadBounds.Y + (_quadBounds.Height / 2);
  172. // Object can fit within the top/bottom quadrants
  173. bool topQuadrant = (entityRectangle.Y < horizontalMidPoint && entityRectangle.Y + entityRectangle.Height < horizontalMidPoint);
  174. bool bottomQuadrant = entityRectangle.Y > horizontalMidPoint;
  175. if (entityRectangle.X < verticalMidPoint && entityRectangle.X + entityRectangle.Width < verticalMidPoint)
  176. {
  177. if (topQuadrant)
  178. index = 1;
  179. else if (bottomQuadrant)
  180. index = 2;
  181. }
  182. else if (entityRectangle.X > verticalMidPoint)
  183. {
  184. if (topQuadrant)
  185. index = 0;
  186. else if (bottomQuadrant)
  187. index = 3;
  188. }
  189. return index;
  190. }
  191. /// <summary>
  192. /// Add a rectangle of an entity.
  193. /// </summary>
  194. /// <param name="entity">Rectangle of an entity</param>
  195. public void Add(ICollidable2 entity)
  196. {
  197. // If the Quadtree is already splited
  198. if (_nodes[0] != null)
  199. {
  200. int index = GetNodeIndex(entity.Rectangle);
  201. if (index > -1)
  202. {
  203. _nodes[index].Add(entity);
  204. return;
  205. }
  206. }
  207. _objects.Add(entity);
  208. // Split the space if we have too many objects. The limit is MaxLevels
  209. if (_objects.Count > _maxObjectsPerNode && _level < _maxSubLevels)
  210. {
  211. if (_nodes[0] == null)
  212. Split();
  213. int i = 0;
  214. while (i < _objects.Count)
  215. {
  216. int index = GetNodeIndex(_objects[i].Rectangle);
  217. if (index > -1)
  218. {
  219. // Add the object to the correct node en remove it from the its parent
  220. _nodes[index].Add(_objects[i]);
  221. _objects.RemoveAt(i);
  222. }
  223. else
  224. i++;
  225. }
  226. }
  227. }
  228. }
  229. }