/src/quadtree.js

https://github.com/abachman/jaws · JavaScript · 211 lines · 124 code · 30 blank · 57 comment · 37 complexity · b1372ce566e14b0e20fd32c89c5e1250 MD5 · raw file

  1. /*
  2. * @class jaws.QuadTree
  3. * @property {jaws.Rect} bounds Rect(x,y,width,height) defining bounds of tree
  4. * @property {number} depth The depth of the root node
  5. * @property {array} nodes The nodes of the root node
  6. * @property {array} objects The objects of the root node
  7. * @example
  8. * setup:
  9. * var quadtree = new jaws.QuadTree();
  10. * update:
  11. * quadtree.collide(sprite or list, sprite or list, callback function);
  12. */
  13. var jaws = (function(jaws) {
  14. /**
  15. * Creates an empty quadtree with optional bounds and starting depth
  16. * @constructor
  17. * @param {jaws.Rect} [bounds] The defining bounds of the tree
  18. * @param {number} [depth] The current depth of the tree
  19. */
  20. jaws.QuadTree = function(bounds) {
  21. this.depth = arguments[1] || 0;
  22. this.bounds = bounds || new jaws.Rect(0, 0, jaws.width, jaws.height);
  23. this.nodes = [];
  24. this.objects = [];
  25. };
  26. /**
  27. * Moves through the nodes and deletes them.
  28. * @this {jaws.QuadTree}
  29. */
  30. jaws.QuadTree.prototype.clear = function() {
  31. this.objects = [];
  32. for (var i = 0; i < this.nodes.length; i++) {
  33. if (typeof this.nodes[i] !== 'undefined') {
  34. this.nodes[i].clear();
  35. delete this.nodes[i];
  36. }
  37. }
  38. };
  39. /**
  40. * Creates four new branches sub-dividing the current node's width and height
  41. * @private
  42. * @this {jaws.QuadTree}
  43. */
  44. jaws.QuadTree.prototype.split = function() {
  45. var subWidth = Math.round((this.bounds.width / 2));
  46. var subHeight = Math.round((this.bounds.height / 2));
  47. var x = this.bounds.x;
  48. var y = this.bounds.y;
  49. this.nodes[0] = new jaws.QuadTree(new jaws.Rect(x + subWidth, y, subWidth, subHeight), this.depth + 1);
  50. this.nodes[1] = new jaws.QuadTree(new jaws.Rect(x, y, subWidth, subHeight), this.depth + 1);
  51. this.nodes[2] = new jaws.QuadTree(new jaws.Rect(x, y + subHeight, subWidth, subHeight), this.depth + 1);
  52. this.nodes[3] = new jaws.QuadTree(new jaws.Rect(x + subWidth, y + subHeight, subWidth, subHeight), this.depth + 1);
  53. };
  54. /**
  55. * Returns the index of a node's branches if passed-in object fits within it
  56. * @private
  57. * @param {object} pRect An object with the properties x, y, width, and height
  58. * @returns {index} The index of nodes[] that matches the dimensions of passed-in object
  59. */
  60. jaws.QuadTree.prototype.getIndex = function(pRect) {
  61. var index = -1;
  62. var verticalMidpoint = this.bounds.x + (this.bounds.width / 2);
  63. var horizontalMidpoint = this.bounds.y + (this.bounds.height / 2);
  64. var topQuadrant = (pRect.y < horizontalMidpoint && pRect.y + pRect.height < horizontalMidpoint);
  65. var bottomQuadrant = (pRect.y > horizontalMidpoint);
  66. if (pRect.x < verticalMidpoint && pRect.x + pRect.width < verticalMidpoint) {
  67. if (topQuadrant) {
  68. index = 1;
  69. }
  70. else if (bottomQuadrant) {
  71. index = 2;
  72. }
  73. }
  74. else if (pRect.x > verticalMidpoint) {
  75. if (topQuadrant) {
  76. index = 0;
  77. }
  78. else if (bottomQuadrant) {
  79. index = 3;
  80. }
  81. }
  82. return index;
  83. };
  84. /**
  85. * Inserts an object into the quadtree, spliting it into new branches if needed
  86. * @param {object} pRect An object with the properties x, y, width, and height
  87. */
  88. jaws.QuadTree.prototype.insert = function(pRect) {
  89. if (!pRect.hasOwnProperty("x") && !pRect.hasOwnProperty("y") &&
  90. !pRect.hasOwnProperty("width") && !pRect.hasOwnProperty("height")) {
  91. return;
  92. }
  93. if (typeof this.nodes[0] !== 'undefined') {
  94. var index = this.getIndex(pRect);
  95. if (index !== -1) {
  96. this.nodes[index].insert(pRect);
  97. return;
  98. }
  99. }
  100. this.objects.push(pRect);
  101. if (typeof this.nodes[0] === 'undefined') {
  102. this.split();
  103. }
  104. var i = 0;
  105. while (i < this.objects.length) {
  106. var index = this.getIndex(this.objects[i]);
  107. if (index !== -1) {
  108. this.nodes[index].insert(this.objects.splice(i, 1)[0]);
  109. }
  110. else {
  111. i++;
  112. }
  113. }
  114. };
  115. /**
  116. * Returns those objects on the branch matching the position of the passed-in object
  117. * @param {object} pRect An object with properties x, y, width, and height
  118. * @returns {array} The objects on the same branch as the passed-in object
  119. */
  120. jaws.QuadTree.prototype.retrieve = function(pRect) {
  121. if (!pRect.hasOwnProperty("x") && !pRect.hasOwnProperty("y") &&
  122. !pRect.hasOwnProperty("width") && !pRect.hasOwnProperty("height")) {
  123. return;
  124. }
  125. var index = this.getIndex(pRect);
  126. var returnObjects = this.objects;
  127. if (typeof this.nodes[0] !== 'undefined') {
  128. if (index !== -1) {
  129. returnObjects = returnObjects.concat(this.nodes[index].retrieve(pRect));
  130. } else {
  131. for (var i = 0; i < this.nodes.length; i++) {
  132. returnObjects = returnObjects.concat(this.nodes[i].retrieve(pRect));
  133. }
  134. }
  135. }
  136. return returnObjects;
  137. };
  138. /**
  139. * Checks for collisions between objects by creating a quadtree, inserting one or more objects,
  140. * and then comparing the results of a retrieval against another single or set of objects.
  141. *
  142. * With the callback argument, it will call a function and pass the items found colliding
  143. * as the first and second argument.
  144. *
  145. * Without the callback argument, it will return a boolean value if any collisions were found.
  146. *
  147. * @param {object|array} list1 A single or set of objects with properties x, y, width, and height
  148. * @param {object|array} list2 A single or set of objects with properties x, y, width, and height
  149. * @param {function} [callback] The function to call per collision
  150. * @returns {boolean} If the items (or any within their sets) collide with one another
  151. */
  152. jaws.QuadTree.prototype.collide = function(list1, list2, callback) {
  153. var overlap = false;
  154. var tree = new jaws.QuadTree();
  155. var temp = [];
  156. if (!(list1.forEach)) {
  157. temp.push(list1);
  158. list1 = temp;
  159. }
  160. if (!(list2.forEach)) {
  161. temp = [];
  162. temp.push(list2);
  163. list2 = temp;
  164. }
  165. list2.forEach(function(el) {
  166. tree.insert(el);
  167. });
  168. list1.forEach(function(el) {
  169. if(jaws.collide(el, tree.retrieve(el), callback)) {
  170. overlap = true;
  171. }
  172. });
  173. tree.clear();
  174. return overlap;
  175. };
  176. return jaws;
  177. })(jaws || {});
  178. // Support CommonJS require()
  179. if (typeof module !== "undefined" && ('exports' in module)) {
  180. module.exports = jaws.QuadTree;
  181. }