1 /**
  2  * @fileOverview Collision Detection
  3  * 
  4  * Collision detection helpers.
  5  *
  6  * @example
  7  *   // collision helper exampels:
  8  *   collideOneWithOne(player, boss)        // -> false
  9  *   collideOneWithMany(player, bullets)    // -> [bullet1, bullet1]
 10  *   collideManyWithMany(bullets, enemies)  // -> [ [bullet1, enemy1], [bullet2, enemy2] ]
 11  *   collide(player, boss)                  // -> false
 12  *   collide(player, 
 13  *           bullets,
 14  *           function(player, bullet) {})   // Callback: arguments[0] -> player 
 15  *                                          //           arguments[1] -> bullets[i]
 16  *
 17  */
 18 var jaws = (function(jaws) {
 19 
 20   /**
 21    * Collides two objects by reading x, y and either method rect() or property radius.
 22    * @public
 23    * @param {object} object1 An object with a 'radius' or 'rect' property
 24    * @param {object} object2 An object with a 'radius' or 'rect' property
 25    * @returns {boolean} If the two objects are colliding or not
 26    */
 27   jaws.collideOneWithOne = function(object1, object2) {
 28     if (object1.radius && object2.radius && object1 !== object2 && jaws.collideCircles(object1, object2))
 29       return true;
 30 
 31     if (object1.rect && object2.rect && object1 !== object2 && jaws.collideRects(object1.rect(), object2.rect()))
 32       return true;
 33 
 34     return false;
 35   };
 36 
 37   /**
 38    * Compares an object against a list, returning those from list that collide with object, and
 39    *  calling 'callback' per collision (if set) with object and item from list.
 40    *  (Note: Will never collide objects with themselves.)
 41    * @public
 42    * @param {object} object An object with a 'radius' or 'rect' property
 43    * @param {array|object} list A collection of objects with a 'length' property
 44    * @param {function} callback The function to be called per collison detected
 45    * @returns {array} A collection of items colliding with object from list
 46    * @example
 47    * collideOneWithMany(player, bullets)    // -> [bullet1, bullet1]
 48    * collideOneWithMany(player, bullets, function(player, bullet) {
 49    *  //player and bullet (bullets[i])
 50    * });
 51    */
 52   jaws.collideOneWithMany = function(object, list, callback) {
 53     var a = [];
 54     if (callback) {
 55       for (var i = 0; i < list.length; i++) {
 56         if (jaws.collideOneWithOne(object, list[i])) {
 57           callback(object, list[i]);
 58           a.push(list[i])
 59         }
 60       }
 61       return a;
 62     }
 63     else {
 64       return list.filter(function(item) {
 65         return jaws.collideOneWithOne(object, item);
 66       });
 67     }
 68   };
 69 
 70   /**
 71    * Compares two lists, returning those items from each that collide with each other, and
 72    *  calling 'callback' per collision (if set) with item from list1 and item from list2.
 73    *  (Note: Will never collide objects with themselves.)
 74    * @public
 75    * @param {array|object} list1 A collection of objects with a 'forEach' property
 76    * @param {array|object} list2 A collection of objects with a 'forEach' property
 77    * @param {function} callback The function to be called per collison detected
 78    * @returns {array} A collection of items colliding with list1 from list2
 79    * @example
 80    *  jaws.collideManyWithMany(bullets, enemies) // --> [[bullet, enemy], [bullet, enemy]]
 81    */
 82   jaws.collideManyWithMany = function(list1, list2, callback) {
 83     var a = [];
 84 
 85     if (list1 === list2) {
 86       combinations(list1, 2).forEach(function(pair) {
 87         if (jaws.collideOneWithOne(pair[0], pair[1])) {
 88           if (callback) {
 89             callback(pair[0], pair[1]);
 90           }
 91           else {
 92             a.push([pair[0], pair[1]]);
 93           }
 94         }
 95       });
 96     }
 97     else {
 98       list1.forEach(function(item1) {
 99         list2.forEach(function(item2) {
100           if (jaws.collideOneWithOne(item1, item2)) {
101             if (callback) {
102               callback(item1, item2);
103             }
104             else {
105               a.push([item1, item2]);
106             }
107           }
108         });
109       });
110     }
111 
112     return a;
113   };
114 
115   /**
116    * Returns if two circle-objects collide with each other
117    * @public
118    * @param {object} object1 An object with a 'radius' property
119    * @param {object} object2 An object with a 'radius' property
120    * @returns {boolean} If two circle-objects collide or not
121    */
122   jaws.collideCircles = function(object1, object2) {
123     return (jaws.distanceBetween(object1, object2) < object1.radius + object2.radius);
124   };
125 
126   /**
127    * Returns if two Rects collide with each other or not
128    * @public
129    * @param {object} rect1 An object with 'x', 'y', 'right' and 'bottom' properties
130    * @param {object} rect2 An object with 'x', 'y', 'right' and 'bottom' properties
131    * @returns {boolean} If two Rects collide with each other or not
132    */
133   jaws.collideRects = function(rect1, rect2) {
134     return ((rect1.x >= rect2.x && rect1.x <= rect2.right) || (rect2.x >= rect1.x && rect2.x <= rect1.right)) &&
135             ((rect1.y >= rect2.y && rect1.y <= rect2.bottom) || (rect2.y >= rect1.y && rect2.y <= rect1.bottom));
136   };
137 
138   /**
139    * Returns the distance between two objects
140    * @public
141    * @param {object} object1 An object with 'x' and 'y' properties
142    * @param {object} object2 An object with 'x' and 'y' properties
143    * @returns {number} The distance between two objects
144    */
145   jaws.distanceBetween = function(object1, object2) {
146     return Math.sqrt(Math.pow(object1.x - object2.x, 2) + Math.pow(object1.y - object2.y, 2));
147   };
148 
149   /**
150    * Creates combinations of items from a list of a specific size
151    * @private
152    * @param {array|object} list An object with a 'length' property
153    * @param {number} n The size of the array to return
154    * @returns {Array} An array of items having a specific size number of its own entries
155    */
156   function combinations(list, n) {
157     var f = function(i) {
158       if (list.isSpriteList !== undefined) {
159         return list.at(i);
160       } else {  // s is an Array
161         return list[i];
162       }
163     };
164     var r = [];
165     var m = new Array(n);
166     for (var i = 0; i < n; i++)
167       m[i] = i;
168     for (var i = n - 1, sn = list.length; 0 <= i; sn = list.length) {
169       r.push(m.map(f));
170       while (0 <= i && m[i] === sn - 1) {
171         i--;
172         sn--;
173       }
174       if (0 <= i) {
175         m[i] += 1;
176         for (var j = i + 1; j < n; j++)
177           m[j] = m[j - 1] + 1;
178         i = n - 1;
179       }
180     }
181     return r;
182   }
183 
184   /**
185    * If an object has items or not
186    * @private
187    * @param {array|object} array An object with a 'length' property
188    * @returns {boolean} If the object has items (length > 0)
189    */
190   function hasItems(array) {
191     return (array && array.length > 0);
192   }
193 
194   /**
195    * Compares two objects or lists, returning if they collide, and 
196    *  calling 'callback' per collision (if set) between objects or lists.
197    * @param {array|object} x An object with either 'rect' or 'forEach' property
198    * @param {array|object} x2 An object with either 'rect' or 'forEach' property
199    * @param {function} callback
200    * @returns {boolean}
201    * @examples
202    *   jaws.collide(player, enemy, function(player, enemy) { ... } )  
203    *   jaws.collide(player, enemies, function(player, enemy) { ... } ) 
204    *   jaws.collide(bullets, enemies, function(bullet, enemy) { ... } )
205    */
206   jaws.collide = function(x, x2, callback) {
207     if ((x.rect || x.radius) && x2.forEach)
208       return (jaws.collideOneWithMany(x, x2, callback).length > 0);
209     if (x.forEach && x2.forEach)
210       return (jaws.collideManyWithMany(x, x2, callback).length > 0);
211     if (x.forEach && (x2.rect || x2.radius))
212       return (jaws.collideOneWithMany(x2, x, callback).length > 0);
213     if ((x.rect && x2.rect) || (x.radius && x2.radius)) {
214       var result = jaws.collideOneWithOne(x, x2);
215       if (callback && result)
216         callback(x, x2);
217       else
218         return result;
219     }
220   };
221 
222   return jaws;
223 })(jaws || {});
224 
225