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