/** * The MIT License (MIT) * * Copyright (c) 2013 p2.js authors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ !function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.p2=e():"undefined"!=typeof global?self.p2=e():"undefined"!=typeof self&&(self.p2=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { //TODO: evaluate use of glm_invsqrt here? len = 1 / Math.sqrt(len); out[0] = a[0] * len; out[1] = a[1] * len; } return out; }; /** * Caclulates the dot product of two vec2's * * @param {vec2} a the first operand * @param {vec2} b the second operand * @returns {Number} dot product of a and b */ vec2.dot = function (a, b) { return a[0] * b[0] + a[1] * b[1]; }; /** * Computes the cross product of two vec2's * Note that the cross product must by definition produce a 3D vector * * @param {vec3} out the receiving vector * @param {vec2} a the first operand * @param {vec2} b the second operand * @returns {vec3} out */ vec2.cross = function(out, a, b) { var z = a[0] * b[1] - a[1] * b[0]; out[0] = out[1] = 0; out[2] = z; return out; }; /** * Performs a linear interpolation between two vec2's * * @param {vec3} out the receiving vector * @param {vec2} a the first operand * @param {vec2} b the second operand * @param {Number} t interpolation amount between the two inputs * @returns {vec2} out */ vec2.lerp = function (out, a, b, t) { var ax = a[0], ay = a[1]; out[0] = ax + t * (b[0] - ax); out[1] = ay + t * (b[1] - ay); return out; }; /** * Transforms the vec2 with a mat2 * * @param {vec2} out the receiving vector * @param {vec2} a the vector to transform * @param {mat2} m matrix to transform with * @returns {vec2} out */ vec2.transformMat2 = function(out, a, m) { var x = a[0], y = a[1]; out[0] = x * m[0] + y * m[1]; out[1] = x * m[2] + y * m[3]; return out; }; /** * Perform some operation over an array of vec2s. * * @param {Array} a the array of vectors to iterate over * @param {Number} stride Number of elements between the start of each vec2. If 0 assumes tightly packed * @param {Number} offset Number of elements to skip at the beginning of the array * @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array * @param {Function} fn Function to call for each vector in the array * @param {Object} [arg] additional argument to pass to fn * @returns {Array} a * @function */ vec2.forEach = (function() { var vec = new Float32Array(2); return function(a, stride, offset, count, fn, arg) { var i, l; if(!stride) { stride = 2; } if(!offset) { offset = 0; } if(count) { l = Math.min((count * stride) + offset, a.length); } else { l = a.length; } for(i = offset; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i+1]; fn(vec, vec, arg); a[i] = vec[0]; a[i+1] = vec[1]; } return a; }; })(); /** * Returns a string representation of a vector * * @param {vec2} vec vector to represent as a string * @returns {String} string representation of the vector */ vec2.str = function (a) { return 'vec2(' + a[0] + ', ' + a[1] + ')'; }; if(typeof(exports) !== 'undefined') { exports.vec2 = vec2; } },{}],3:[function(require,module,exports){ var Scalar = require('./Scalar'); module.exports = Line; /** * Container for line-related functions * @class Line */ function Line(){}; /** * Compute the intersection between two lines. * @static * @method lineInt * @param {Array} l1 Line vector 1 * @param {Array} l2 Line vector 2 * @param {Number} precision Precision to use when checking if the lines are parallel * @return {Array} The intersection point. */ Line.lineInt = function(l1,l2,precision){ precision = precision || 0; var i = [0,0]; // point var a1, b1, c1, a2, b2, c2, det; // scalars a1 = l1[1][1] - l1[0][1]; b1 = l1[0][0] - l1[1][0]; c1 = a1 * l1[0][0] + b1 * l1[0][1]; a2 = l2[1][1] - l2[0][1]; b2 = l2[0][0] - l2[1][0]; c2 = a2 * l2[0][0] + b2 * l2[0][1]; det = a1 * b2 - a2*b1; if (!Scalar.eq(det, 0, precision)) { // lines are not parallel i[0] = (b2 * c1 - b1 * c2) / det; i[1] = (a1 * c2 - a2 * c1) / det; } return i; }; /** * Checks if two line segments intersects. * @method segmentsIntersect * @param {Array} p1 The start vertex of the first line segment. * @param {Array} p2 The end vertex of the first line segment. * @param {Array} q1 The start vertex of the second line segment. * @param {Array} q2 The end vertex of the second line segment. * @return {Boolean} True if the two line segments intersect */ Line.segmentsIntersect = function(p1, p2, q1, q2){ var dx = p2[0] - p1[0]; var dy = p2[1] - p1[1]; var da = q2[0] - q1[0]; var db = q2[1] - q1[1]; // segments are parallel if(da*dy - db*dx == 0) return false; var s = (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx) var t = (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy) return (s>=0 && s<=1 && t>=0 && t<=1); }; },{"./Scalar":6}],4:[function(require,module,exports){ module.exports = Point; /** * Point related functions * @class Point */ function Point(){}; /** * Get the area of a triangle spanned by the three given points. Note that the area will be negative if the points are not given in counter-clockwise order. * @static * @method area * @param {Array} a * @param {Array} b * @param {Array} c * @return {Number} */ Point.area = function(a,b,c){ return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1]))); }; Point.left = function(a,b,c){ return Point.area(a,b,c) > 0; }; Point.leftOn = function(a,b,c) { return Point.area(a, b, c) >= 0; }; Point.right = function(a,b,c) { return Point.area(a, b, c) < 0; }; Point.rightOn = function(a,b,c) { return Point.area(a, b, c) <= 0; }; var tmpPoint1 = [], tmpPoint2 = []; /** * Check if three points are collinear * @method collinear * @param {Array} a * @param {Array} b * @param {Array} c * @param {Number} [thresholdAngle=0] Threshold angle to use when comparing the vectors. The function will return true if the angle between the resulting vectors is less than this value. Use zero for max precision. * @return {Boolean} */ Point.collinear = function(a,b,c,thresholdAngle) { if(!thresholdAngle) return Point.area(a, b, c) == 0; else { var ab = tmpPoint1, bc = tmpPoint2; ab[0] = b[0]-a[0]; ab[1] = b[1]-a[1]; bc[0] = c[0]-b[0]; bc[1] = c[1]-b[1]; var dot = ab[0]*bc[0] + ab[1]*bc[1], magA = Math.sqrt(ab[0]*ab[0] + ab[1]*ab[1]), magB = Math.sqrt(bc[0]*bc[0] + bc[1]*bc[1]), angle = Math.acos(dot/(magA*magB)); return angle < thresholdAngle; } }; Point.sqdist = function(a,b){ var dx = b[0] - a[0]; var dy = b[1] - a[1]; return dx * dx + dy * dy; }; },{}],5:[function(require,module,exports){ var Line = require("./Line") , Point = require("./Point") , Scalar = require("./Scalar") module.exports = Polygon; /** * Polygon class. * @class Polygon * @constructor */ function Polygon(){ /** * Vertices that this polygon consists of. An array of array of numbers, example: [[0,0],[1,0],..] * @property vertices * @type {Array} */ this.vertices = []; } /** * Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle. * @method at * @param {Number} i * @return {Array} */ Polygon.prototype.at = function(i){ var v = this.vertices, s = v.length; return v[i < 0 ? i % s + s : i % s]; }; /** * Get first vertex * @method first * @return {Array} */ Polygon.prototype.first = function(){ return this.vertices[0]; }; /** * Get last vertex * @method last * @return {Array} */ Polygon.prototype.last = function(){ return this.vertices[this.vertices.length-1]; }; /** * Clear the polygon data * @method clear * @return {Array} */ Polygon.prototype.clear = function(){ this.vertices.length = 0; }; /** * Append points "from" to "to"-1 from an other polygon "poly" onto this one. * @method append * @param {Polygon} poly The polygon to get points from. * @param {Number} from The vertex index in "poly". * @param {Number} to The end vertex index in "poly". Note that this vertex is NOT included when appending. * @return {Array} */ Polygon.prototype.append = function(poly,from,to){ if(typeof(from) == "undefined") throw new Error("From is not given!"); if(typeof(to) == "undefined") throw new Error("To is not given!"); if(to-1 < from) throw new Error("lol1"); if(to > poly.vertices.length) throw new Error("lol2"); if(from < 0) throw new Error("lol3"); for(var i=from; i v[br][0])) { br = i; } } // reverse poly if clockwise if (!Point.left(this.at(br - 1), this.at(br), this.at(br + 1))) { this.reverse(); } }; /** * Reverse the vertices in the polygon * @method reverse */ Polygon.prototype.reverse = function(){ var tmp = []; for(var i=0, N=this.vertices.length; i!==N; i++){ tmp.push(this.vertices.pop()); } this.vertices = tmp; }; /** * Check if a point in the polygon is a reflex point * @method isReflex * @param {Number} i * @return {Boolean} */ Polygon.prototype.isReflex = function(i){ return Point.right(this.at(i - 1), this.at(i), this.at(i + 1)); }; var tmpLine1=[], tmpLine2=[]; /** * Check if two vertices in the polygon can see each other * @method canSee * @param {Number} a Vertex index 1 * @param {Number} b Vertex index 2 * @return {Boolean} */ Polygon.prototype.canSee = function(a,b) { var p, dist, l1=tmpLine1, l2=tmpLine2; if (Point.leftOn(this.at(a + 1), this.at(a), this.at(b)) && Point.rightOn(this.at(a - 1), this.at(a), this.at(b))) { return false; } dist = Point.sqdist(this.at(a), this.at(b)); for (var i = 0; i !== this.vertices.length; ++i) { // for each edge if ((i + 1) % this.vertices.length === a || i === a) // ignore incident edges continue; if (Point.leftOn(this.at(a), this.at(b), this.at(i + 1)) && Point.rightOn(this.at(a), this.at(b), this.at(i))) { // if diag intersects an edge l1[0] = this.at(a); l1[1] = this.at(b); l2[0] = this.at(i); l2[1] = this.at(i + 1); p = Line.lineInt(l1,l2); if (Point.sqdist(this.at(a), p) < dist) { // if edge is blocking visibility to b return false; } } } return true; }; /** * Copy the polygon from vertex i to vertex j. * @method copy * @param {Number} i * @param {Number} j * @param {Polygon} [targetPoly] Optional target polygon to save in. * @return {Polygon} The resulting copy. */ Polygon.prototype.copy = function(i,j,targetPoly){ var p = targetPoly || new Polygon(); p.clear(); if (i < j) { // Insert all vertices from i to j for(var k=i; k<=j; k++) p.vertices.push(this.vertices[k]); } else { // Insert vertices 0 to j for(var k=0; k<=j; k++) p.vertices.push(this.vertices[k]); // Insert vertices i to end for(var k=i; k 0) return this.slice(edges); else return [this]; }; /** * Slices the polygon given one or more cut edges. If given one, this function will return two polygons (false on failure). If many, an array of polygons. * @method slice * @param {Array} cutEdges A list of edges, as returned by .getCutEdges() * @return {Array} */ Polygon.prototype.slice = function(cutEdges){ if(cutEdges.length == 0) return [this]; if(cutEdges instanceof Array && cutEdges.length && cutEdges[0] instanceof Array && cutEdges[0].length==2 && cutEdges[0][0] instanceof Array){ var polys = [this]; for(var i=0; i maxlevel){ console.warn("quickDecomp: max level ("+maxlevel+") reached."); return result; } for (var i = 0; i < this.vertices.length; ++i) { if (poly.isReflex(i)) { reflexVertices.push(poly.vertices[i]); upperDist = lowerDist = Number.MAX_VALUE; for (var j = 0; j < this.vertices.length; ++j) { if (Point.left(poly.at(i - 1), poly.at(i), poly.at(j)) && Point.rightOn(poly.at(i - 1), poly.at(i), poly.at(j - 1))) { // if line intersects with an edge p = getIntersectionPoint(poly.at(i - 1), poly.at(i), poly.at(j), poly.at(j - 1)); // find the point of intersection if (Point.right(poly.at(i + 1), poly.at(i), p)) { // make sure it's inside the poly d = Point.sqdist(poly.vertices[i], p); if (d < lowerDist) { // keep only the closest intersection lowerDist = d; lowerInt = p; lowerIndex = j; } } } if (Point.left(poly.at(i + 1), poly.at(i), poly.at(j + 1)) && Point.rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) { p = getIntersectionPoint(poly.at(i + 1), poly.at(i), poly.at(j), poly.at(j + 1)); if (Point.left(poly.at(i - 1), poly.at(i), p)) { d = Point.sqdist(poly.vertices[i], p); if (d < upperDist) { upperDist = d; upperInt = p; upperIndex = j; } } } } // if there are no vertices to connect to, choose a point in the middle if (lowerIndex == (upperIndex + 1) % this.vertices.length) { //console.log("Case 1: Vertex("+i+"), lowerIndex("+lowerIndex+"), upperIndex("+upperIndex+"), poly.size("+this.vertices.length+")"); p[0] = (lowerInt[0] + upperInt[0]) / 2; p[1] = (lowerInt[1] + upperInt[1]) / 2; steinerPoints.push(p); if (i < upperIndex) { //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.begin() + upperIndex + 1); lowerPoly.append(poly, i, upperIndex+1); lowerPoly.vertices.push(p); upperPoly.vertices.push(p); if (lowerIndex != 0){ //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.end()); upperPoly.append(poly,lowerIndex,poly.vertices.length); } //upperPoly.insert(upperPoly.end(), poly.begin(), poly.begin() + i + 1); upperPoly.append(poly,0,i+1); } else { if (i != 0){ //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.end()); lowerPoly.append(poly,i,poly.vertices.length); } //lowerPoly.insert(lowerPoly.end(), poly.begin(), poly.begin() + upperIndex + 1); lowerPoly.append(poly,0,upperIndex+1); lowerPoly.vertices.push(p); upperPoly.vertices.push(p); //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.begin() + i + 1); upperPoly.append(poly,lowerIndex,i+1); } } else { // connect to the closest point within the triangle //console.log("Case 2: Vertex("+i+"), closestIndex("+closestIndex+"), poly.size("+this.vertices.length+")\n"); if (lowerIndex > upperIndex) { upperIndex += this.vertices.length; } closestDist = Number.MAX_VALUE; if(upperIndex < lowerIndex){ return result; } for (var j = lowerIndex; j <= upperIndex; ++j) { if (Point.leftOn(poly.at(i - 1), poly.at(i), poly.at(j)) && Point.rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) { d = Point.sqdist(poly.at(i), poly.at(j)); if (d < closestDist) { closestDist = d; closestIndex = j % this.vertices.length; } } } if (i < closestIndex) { lowerPoly.append(poly,i,closestIndex+1); if (closestIndex != 0){ upperPoly.append(poly,closestIndex,v.length); } upperPoly.append(poly,0,i+1); } else { if (i != 0){ lowerPoly.append(poly,i,v.length); } lowerPoly.append(poly,0,closestIndex+1); upperPoly.append(poly,closestIndex,i+1); } } // solve smallest poly first if (lowerPoly.vertices.length < upperPoly.vertices.length) { lowerPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); upperPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); } else { upperPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); lowerPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); } return result; } } result.push(this); return result; }; /** * Remove collinear points in the polygon. * @method removeCollinearPoints * @param {Number} [precision] The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision. * @return {Number} The number of points removed */ Polygon.prototype.removeCollinearPoints = function(precision){ var num = 0; for(var i=this.vertices.length-1; this.vertices.length>3 && i>=0; --i){ if(Point.collinear(this.at(i-1),this.at(i),this.at(i+1),precision)){ // Remove the middle point this.vertices.splice(i%this.vertices.length,1); i--; // Jump one point forward. Otherwise we may get a chain removal num++; } } return num; }; },{"./Line":3,"./Point":4,"./Scalar":6}],6:[function(require,module,exports){ module.exports = Scalar; /** * Scalar functions * @class Scalar */ function Scalar(){} /** * Check if two scalars are equal * @static * @method eq * @param {Number} a * @param {Number} b * @param {Number} [precision] * @return {Boolean} */ Scalar.eq = function(a,b,precision){ precision = precision || 0; return Math.abs(a-b) < precision; }; },{}],7:[function(require,module,exports){ module.exports = { Polygon : require("./Polygon"), Point : require("./Point"), }; },{"./Point":4,"./Polygon":5}],8:[function(require,module,exports){ module.exports={ "name": "p2", "version": "0.4.0", "description": "A JavaScript 2D physics engine.", "author": "Stefan Hedman (http://steffe.se)", "keywords": [ "p2.js", "p2", "physics", "engine", "2d" ], "main": "./src/p2.js", "engines": { "node": "*" }, "repository": { "type": "git", "url": "https://github.com/schteppe/p2.js.git" }, "bugs": { "url": "https://github.com/schteppe/p2.js/issues" }, "licenses" : [ { "type" : "MIT" } ], "devDependencies" : { "jshint" : "latest", "nodeunit" : "latest", "grunt": "~0.4.0", "grunt-contrib-jshint": "~0.1.1", "grunt-contrib-nodeunit": "~0.1.2", "grunt-contrib-concat": "~0.1.3", "grunt-contrib-uglify": "*", "grunt-browserify" : "*", "browserify":"*" }, "dependencies" : { "underscore":"*", "poly-decomp" : "git://github.com/schteppe/poly-decomp.js", "gl-matrix":"2.0.0", "jsonschema":"*" } } },{}],9:[function(require,module,exports){ var vec2 = require('../math/vec2') , Utils = require('../utils/Utils') module.exports = AABB; /** * Axis aligned bounding box class. * @class AABB * @constructor * @param {Object} options * @param {Array} upperBound * @param {Array} lowerBound */ function AABB(options){ /** * The lower bound of the bounding box. * @property lowerBound * @type {Array} */ this.lowerBound = vec2.create(); if(options && options.lowerBound) vec2.copy(this.lowerBound, options.lowerBound); /** * The upper bound of the bounding box. * @property upperBound * @type {Array} */ this.upperBound = vec2.create(); if(options && options.upperBound) vec2.copy(this.upperBound, options.upperBound); } var tmp = vec2.create(); /** * Set the AABB bounds from a set of points. * @method setFromPoints * @param {Array} points An array of vec2's. */ AABB.prototype.setFromPoints = function(points,position,angle){ var l = this.lowerBound, u = this.upperBound; vec2.set(l, Number.MAX_VALUE, Number.MAX_VALUE); vec2.set(u, -Number.MAX_VALUE, -Number.MAX_VALUE); for(var i=0; i u[j]){ u[j] = p[j]; } if(p[j] < l[j]){ l[j] = p[j]; } } } // Add offset if(position){ vec2.add(this.lowerBound, this.lowerBound, position); vec2.add(this.upperBound, this.upperBound, position); } }; /** * Copy bounds from an AABB to this AABB * @method copy * @param {AABB} aabb */ AABB.prototype.copy = function(aabb){ vec2.copy(this.lowerBound, aabb.lowerBound); vec2.copy(this.upperBound, aabb.upperBound); }; /** * Extend this AABB so that it covers the given AABB too. * @method extend * @param {AABB} aabb */ AABB.prototype.extend = function(aabb){ // Loop over x and y for(var i=0; i<2; i++){ // Extend lower bound if(aabb.lowerBound[i] < this.lowerBound[i]) this.lowerBound[i] = aabb.lowerBound[i]; // Upper if(aabb.upperBound[i] > this.upperBound[i]) this.upperBound[i] = aabb.upperBound[i]; } }; /** * Returns true if the given AABB overlaps this AABB. * @method overlaps * @param {AABB} aabb * @return {Boolean} */ AABB.prototype.overlaps = function(aabb){ var l1 = this.lowerBound, u1 = this.upperBound, l2 = aabb.lowerBound, u2 = aabb.upperBound; // l2 u2 // |---------| // |--------| // l1 u1 return ((l2[0] <= u1[0] && u1[0] <= u2[0]) || (l1[0] <= u2[0] && u2[0] <= u1[0])) && ((l2[1] <= u1[1] && u1[1] <= u2[1]) || (l1[1] <= u2[1] && u2[1] <= u1[1])); }; },{"../math/vec2":33,"../utils/Utils":49}],10:[function(require,module,exports){ var vec2 = require('../math/vec2') var Body = require('../objects/Body') module.exports = Broadphase; /** * Base class for broadphase implementations. * @class Broadphase * @constructor */ function Broadphase(){ /** * The resulting overlapping pairs. Will be filled with results during .getCollisionPairs(). * @property result * @type {Array} */ this.result = []; /** * The world to search for collision pairs in. To change it, use .setWorld() * @property world * @type {World} */ this.world = null; }; /** * Set the world that we are searching for collision pairs in * @method setWorld * @param {World} world */ Broadphase.prototype.setWorld = function(world){ this.world = world; }; /** * Get all potential intersecting body pairs. * @method getCollisionPairs * @param {World} world The world to search in. * @return {Array} An array of the bodies, ordered in pairs. Example: A result of [a,b,c,d] means that the potential pairs are: (a,b), (c,d). */ Broadphase.prototype.getCollisionPairs = function(world){ throw new Error("getCollisionPairs must be implemented in a subclass!"); }; var dist = vec2.create(); /** * Check whether the bounding radius of two bodies overlap. * @method boundingRadiusCheck * @param {Body} bodyA * @param {Body} bodyB * @return {Boolean} */ Broadphase.boundingRadiusCheck = function(bodyA, bodyB){ vec2.sub(dist, bodyA.position, bodyB.position); var d2 = vec2.squaredLength(dist), r = bodyA.boundingRadius + bodyB.boundingRadius; return d2 <= r*r; }; /** * Check whether the bounding radius of two bodies overlap. * @method boundingRadiusCheck * @param {Body} bodyA * @param {Body} bodyB * @return {Boolean} */ Broadphase.aabbCheck = function(bodyA, bodyB){ if(bodyA.aabbNeedsUpdate) bodyA.updateAABB(); if(bodyB.aabbNeedsUpdate) bodyB.updateAABB(); return bodyA.aabb.overlaps(bodyB.aabb); }; /** * Check whether two bodies are allowed to collide at all. * @method canCollide * @param {Body} bodyA * @param {Body} bodyB * @return {Boolean} */ Broadphase.canCollide = function(bodyA, bodyB){ // Cannot collide static bodies if(bodyA.motionState & Body.STATIC && bodyB.motionState & Body.STATIC) return false; // Cannot collide sleeping bodies if(bodyA.sleepState & Body.SLEEPING && bodyB.sleepState & Body.SLEEPING) return false; return true; }; },{"../math/vec2":33,"../objects/Body":34}],11:[function(require,module,exports){ var Circle = require('../shapes/Circle') , Plane = require('../shapes/Plane') , Particle = require('../shapes/Particle') , Broadphase = require('../collision/Broadphase') , vec2 = require('../math/vec2') module.exports = GridBroadphase; /** * Broadphase that uses axis-aligned bins. * @class GridBroadphase * @constructor * @extends Broadphase * @param {number} xmin Lower x bound of the grid * @param {number} xmax Upper x bound * @param {number} ymin Lower y bound * @param {number} ymax Upper y bound * @param {number} nx Number of bins along x axis * @param {number} ny Number of bins along y axis * @todo test */ function GridBroadphase(xmin,xmax,ymin,ymax,nx,ny){ Broadphase.apply(this); nx = nx || 10; ny = ny || 10; this.binsizeX = (xmax-xmin) / nx; this.binsizeY = (ymax-ymin) / ny; this.nx = nx; this.ny = ny; this.xmin = xmin; this.ymin = ymin; this.xmax = xmax; this.ymax = ymax; }; GridBroadphase.prototype = new Broadphase(); /** * Get a bin index given a world coordinate * @method getBinIndex * @param {Number} x * @param {Number} y * @return {Number} Integer index */ GridBroadphase.prototype.getBinIndex = function(x,y){ var nx = this.nx, ny = this.ny, xmin = this.xmin, ymin = this.ymin, xmax = this.xmax, ymax = this.ymax; var xi = Math.floor(nx * (x - xmin) / (xmax-xmin)); var yi = Math.floor(ny * (y - ymin) / (ymax-ymin)); return xi*ny + yi; } /** * Get collision pairs. * @method getCollisionPairs * @param {World} world * @return {Array} */ GridBroadphase.prototype.getCollisionPairs = function(world){ var result = [], collidingBodies = world.bodies, Ncolliding = Ncolliding=collidingBodies.length, binsizeX = this.binsizeX, binsizeY = this.binsizeY; var bins=[], Nbins=nx*ny; for(var i=0; i= 0 && xi*(ny-1) + yi < Nbins) bins[ xi*(ny-1) + yi ].push(bi); } } } else if(si instanceof Plane){ // Put in all bins for now if(bi.angle == 0){ var y = bi.position[1]; for(var j=0; j!==Nbins && ymin+binsizeY*(j-1) id2){ var tmp = id1; id1 = id2; id2 = tmp; } return !!this.collidingBodiesLastStep[id1 + " " + id2]; }; // "for in" loops aren't optimised in chrome... is there a better way to handle last-step collision memory? // Maybe do this: http://jsperf.com/reflection-vs-array-of-keys function clearObject(obj){ for(var i = 0, l = obj.keys.length; i < l; i++) { delete obj[obj.keys[i]]; } obj.keys.length = 0; /* for(var key in this.collidingBodiesLastStep) delete this.collidingBodiesLastStep[key]; */ } /** * Throws away the old equations and gets ready to create new * @method reset * @param {World} world */ Narrowphase.prototype.reset = function(world){ // Save the colliding bodies data clearObject(this.collidingBodiesLastStep); for(var i=0; i!==this.contactEquations.length; i++){ var eq = this.contactEquations[i], id1 = eq.bi.id, id2 = eq.bj.id; if(id1 > id2){ var tmp = id1; id1 = id2; id2 = tmp; } var key = id1 + " " + id2; if(!this.collidingBodiesLastStep[key]){ this.collidingBodiesLastStep[key] = true; this.collidingBodiesLastStep.keys.push(key); } } if(this.reuseObjects){ var ce = this.contactEquations, fe = this.frictionEquations, rfe = this.reusableFrictionEquations, rce = this.reusableContactEquations; Utils.appendArray(rce,ce); Utils.appendArray(rfe,fe); } // Reset this.contactEquations.length = this.frictionEquations.length = 0; }; /** * Creates a ContactEquation, either by reusing an existing object or creating a new one. * @method createContactEquation * @param {Body} bodyA * @param {Body} bodyB * @return {ContactEquation} */ Narrowphase.prototype.createContactEquation = function(bodyA,bodyB,shapeA,shapeB){ var c = this.reusableContactEquations.length ? this.reusableContactEquations.pop() : new ContactEquation(bodyA,bodyB); c.bi = bodyA; c.bj = bodyB; c.shapeA = shapeA; c.shapeB = shapeB; c.restitution = this.restitution; c.firstImpact = !this.collidedLastStep(bodyA,bodyB); c.enabled = true; if(bodyA.allowSleep && (bodyA.motionState & Body.DYNAMIC) && !(bodyB.motionState & Body.STATIC || bodyB.sleepState === Body.SLEEPY)) bodyA.wakeUp(); if(bodyB.allowSleep && (bodyB.motionState & Body.DYNAMIC) && !(bodyA.motionState & Body.STATIC || bodyA.sleepState === Body.SLEEPY)) bodyB.wakeUp(); return c; }; /** * Creates a FrictionEquation, either by reusing an existing object or creating a new one. * @method createFrictionEquation * @param {Body} bodyA * @param {Body} bodyB * @return {FrictionEquation} */ Narrowphase.prototype.createFrictionEquation = function(bodyA,bodyB,shapeA,shapeB){ var c = this.reusableFrictionEquations.length ? this.reusableFrictionEquations.pop() : new FrictionEquation(bodyA,bodyB); c.bi = bodyA; c.bj = bodyB; c.shapeA = shapeA; c.shapeB = shapeB; c.setSlipForce(this.slipForce); c.frictionCoefficient = this.frictionCoefficient; c.relativeVelocity = this.surfaceVelocity; c.enabled = true; return c; }; /** * Creates a FrictionEquation given the data in the ContactEquation. Uses same offset vectors ri and rj, but the tangent vector will be constructed from the collision normal. * @method createFrictionFromContact * @param {ContactEquation} contactEquation * @return {FrictionEquation} */ Narrowphase.prototype.createFrictionFromContact = function(c){ var eq = this.createFrictionEquation(c.bi,c.bj,c.shapeA,c.shapeB); vec2.copy(eq.ri, c.ri); vec2.copy(eq.rj, c.rj); vec2.rotate(eq.t, c.ni, -Math.PI / 2); eq.contactEquation = c; return eq; } /** * Convex/line narrowphase * @method convexLine * @param {Body} bi * @param {Convex} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Line} sj * @param {Array} xj * @param {Number} aj * @todo Implement me! */ Narrowphase.prototype[Shape.LINE | Shape.CONVEX] = Narrowphase.prototype.convexLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ // TODO if(justTest) return false; else return 0; }; /** * Line/rectangle narrowphase * @method lineRectangle * @param {Body} bi * @param {Line} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Rectangle} sj * @param {Array} xj * @param {Number} aj * @todo Implement me! */ Narrowphase.prototype[Shape.LINE | Shape.RECTANGLE] = Narrowphase.prototype.lineRectangle = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ // TODO if(justTest) return false; else return 0; }; /** * Rectangle/capsule narrowphase * @method rectangleCapsule * @param {Body} bi * @param {Rectangle} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Capsule} sj * @param {Array} xj * @param {Number} aj * @todo Implement me! */ Narrowphase.prototype[Shape.CAPSULE | Shape.RECTANGLE] = Narrowphase.prototype.rectangleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ // TODO if(justTest) return false; else return 0; }; /** * Convex/capsule narrowphase * @method convexCapsule * @param {Body} bi * @param {Convex} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Capsule} sj * @param {Array} xj * @param {Number} aj * @todo Implement me! */ Narrowphase.prototype[Shape.CAPSULE | Shape.CONVEX] = Narrowphase.prototype.convexCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ // TODO if(justTest) return false; else return 0; }; /** * Capsule/line narrowphase * @method lineCapsule * @param {Body} bi * @param {Line} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Capsule} sj * @param {Array} xj * @param {Number} aj * @todo Implement me! */ Narrowphase.prototype[Shape.CAPSULE | Shape.LINE] = Narrowphase.prototype.lineCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ // TODO if(justTest) return false; else return 0; }; /** * Capsule/capsule narrowphase * @method capsuleCapsule * @param {Body} bi * @param {Capsule} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Capsule} sj * @param {Array} xj * @param {Number} aj * @todo Implement me! */ Narrowphase.prototype[Shape.CAPSULE | Shape.CAPSULE] = Narrowphase.prototype.capsuleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ // TODO if(justTest) return false; else return 0; }; /** * Line/line narrowphase * @method lineLine * @param {Body} bi * @param {Line} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Line} sj * @param {Array} xj * @param {Number} aj * @todo Implement me! */ Narrowphase.prototype[Shape.LINE | Shape.LINE] = Narrowphase.prototype.lineLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ // TODO if(justTest) return false; else return 0; }; /** * Plane/line Narrowphase * @method planeLine * @param {Body} planeBody * @param {Plane} planeShape * @param {Array} planeOffset * @param {Number} planeAngle * @param {Body} lineBody * @param {Line} lineShape * @param {Array} lineOffset * @param {Number} lineAngle */ Narrowphase.prototype[Shape.PLANE | Shape.LINE] = Narrowphase.prototype.planeLine = function(planeBody, planeShape, planeOffset, planeAngle, lineBody, lineShape, lineOffset, lineAngle, justTest){ var worldVertex0 = tmp1, worldVertex1 = tmp2, worldVertex01 = tmp3, worldVertex11 = tmp4, worldEdge = tmp5, worldEdgeUnit = tmp6, dist = tmp7, worldNormal = tmp8, worldTangent = tmp9, verts = tmpArray numContacts = 0; // Get start and end points vec2.set(worldVertex0, -lineShape.length/2, 0); vec2.set(worldVertex1, lineShape.length/2, 0); // Not sure why we have to use worldVertex*1 here, but it won't work otherwise. Tired. vec2.rotate(worldVertex01, worldVertex0, lineAngle); vec2.rotate(worldVertex11, worldVertex1, lineAngle); add(worldVertex01, worldVertex01, lineOffset); add(worldVertex11, worldVertex11, lineOffset); vec2.copy(worldVertex0,worldVertex01); vec2.copy(worldVertex1,worldVertex11); // Get vector along the line sub(worldEdge, worldVertex1, worldVertex0); vec2.normalize(worldEdgeUnit, worldEdge); // Get tangent to the edge. vec2.rotate(worldTangent, worldEdgeUnit, -Math.PI/2); vec2.rotate(worldNormal, yAxis, planeAngle); // Check line ends verts[0] = worldVertex0; verts[1] = worldVertex1; for(var i=0; i pos0 && pos < pos1){ // We got contact! if(justTest) return true; var c = this.createContactEquation(circleBody,lineBody,si,sj); vec2.scale(c.ni, orthoDist, -1); vec2.normalize(c.ni, c.ni); vec2.scale( c.ri, c.ni, circleRadius); add(c.ri, c.ri, circleOffset); sub(c.ri, c.ri, circleBody.position); sub(c.rj, projectedPoint, lineOffset); add(c.rj, c.rj, lineOffset); sub(c.rj, c.rj, lineBody.position); this.contactEquations.push(c); if(this.enableFriction){ this.frictionEquations.push(this.createFrictionFromContact(c)); } return 1; } } // Add corner // @todo reuse array object verts[0] = worldVertex0; verts[1] = worldVertex1; for(var i=0; i 0){ // Now project the circle onto the edge vec2.scale(orthoDist, worldTangent, d); sub(projectedPoint, circleOffset, orthoDist); // Check if the point is within the edge span var pos = dot(worldEdgeUnit, projectedPoint); var pos0 = dot(worldEdgeUnit, worldVertex0); var pos1 = dot(worldEdgeUnit, worldVertex1); if(pos > pos0 && pos < pos1){ // We got contact! if(justTest) return true; if(closestEdgeDistance === null || d*d 0){ for(var i=0; i= 0){ // Now project the particle onto the edge vec2.scale(orthoDist, worldTangent, d); sub(projectedPoint, particleOffset, orthoDist); // Check if the point is within the edge span var pos = dot(worldEdgeUnit, projectedPoint); var pos0 = dot(worldEdgeUnit, worldVertex0); var pos1 = dot(worldEdgeUnit, worldVertex1); if(pos > pos0 && pos < pos1){ // We got contact! if(justTest) return true; if(closestEdgeDistance === null || d*d r*r) return 0; if(justTest) return true; var c = this.createContactEquation(bodyA,bodyB,si,sj); sub(c.ni, offsetB, offsetA); vec2.normalize(c.ni,c.ni); vec2.scale( c.ri, c.ni, shapeA.radius); vec2.scale( c.rj, c.ni, -shapeB.radius); add(c.ri, c.ri, offsetA); sub(c.ri, c.ri, bodyA.position); add(c.rj, c.rj, offsetB); sub(c.rj, c.rj, bodyB.position); this.contactEquations.push(c); if(this.enableFriction){ this.frictionEquations.push(this.createFrictionFromContact(c)); } return 1; }; /** * Plane/Convex Narrowphase * @method planeConvex * @param {Body} bi * @param {Plane} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Convex} sj * @param {Array} xj * @param {Number} aj */ Narrowphase.prototype[Shape.PLANE | Shape.CONVEX] = Narrowphase.prototype.planeConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ var convexBody = bj, convexOffset = xj, convexShape = sj, convexAngle = aj, planeBody = bi, planeShape = si, planeOffset = xi, planeAngle = ai; var worldVertex = tmp1, worldNormal = tmp2, dist = tmp3; var numReported = 0; vec2.rotate(worldNormal, yAxis, planeAngle); for(var i=0; i= 2) break; } } return numReported; }; /** * @method convexPlane * @deprecated Use .planeConvex() instead! */ Narrowphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ console.warn("Narrowphase.prototype.convexPlane is deprecated. Use planeConvex instead!"); return this.planeConvex( bj,sj,xj,aj, bi,si,xi,ai, justTest ); } /** * Narrowphase for particle vs plane * @method particlePlane * @param {Body} bi The particle body * @param {Particle} si Particle shape * @param {Array} xi World position for the particle * @param {Number} ai World angle for the particle * @param {Body} bj Plane body * @param {Plane} sj Plane shape * @param {Array} xj World position for the plane * @param {Number} aj World angle for the plane */ Narrowphase.prototype[Shape.PARTICLE | Shape.PLANE] = Narrowphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ var particleBody = bi, particleShape = si, particleOffset = xi, planeBody = bj, planeShape = sj, planeOffset = xj, planeAngle = aj; var dist = tmp1, worldNormal = tmp2; planeAngle = planeAngle || 0; sub(dist, particleOffset, planeOffset); vec2.rotate(worldNormal, yAxis, planeAngle); var d = dot(dist, worldNormal); if(d > 0) return 0; if(justTest) return true; var c = this.createContactEquation(planeBody,particleBody,sj,si); vec2.copy(c.ni, worldNormal); vec2.scale( dist, c.ni, d ); // dist is now the distance vector in the normal direction // ri is the particle position projected down onto the plane, from the plane center sub( c.ri, particleOffset, dist); sub( c.ri, c.ri, planeBody.position); // rj is from the body center to the particle center sub( c.rj, particleOffset, particleBody.position ); this.contactEquations.push(c); if(this.enableFriction){ this.frictionEquations.push(this.createFrictionFromContact(c)); } return 1; }; /** * Circle/Particle Narrowphase * @method circleParticle * @param {Body} bi * @param {Circle} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Particle} sj * @param {Array} xj * @param {Number} aj */ Narrowphase.prototype[Shape.CIRCLE | Shape.PARTICLE] = Narrowphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ var circleBody = bi, circleShape = si, circleOffset = xi, particleBody = bj, particleShape = sj, particleOffset = xj, dist = tmp1; sub(dist, particleOffset, circleOffset); if(vec2.squaredLength(dist) > circleShape.radius*circleShape.radius) return 0; if(justTest) return true; var c = this.createContactEquation(circleBody,particleBody,si,sj); vec2.copy(c.ni, dist); vec2.normalize(c.ni,c.ni); // Vector from circle to contact point is the normal times the circle radius vec2.scale(c.ri, c.ni, circleShape.radius); add(c.ri, c.ri, circleOffset); sub(c.ri, c.ri, circleBody.position); // Vector from particle center to contact point is zero sub(c.rj, particleOffset, particleBody.position); this.contactEquations.push(c); if(this.enableFriction){ this.frictionEquations.push(this.createFrictionFromContact(c)); } return 1; }; var capsulePlane_tmpCircle = new Circle(1), capsulePlane_tmp1 = vec2.create(), capsulePlane_tmp2 = vec2.create(), capsulePlane_tmp3 = vec2.create(); Narrowphase.prototype[Shape.PLANE | Shape.CAPSULE] = Narrowphase.prototype.planeCapsule = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ var end1 = capsulePlane_tmp1, end2 = capsulePlane_tmp2, circle = capsulePlane_tmpCircle, dst = capsulePlane_tmp3; // Compute world end positions vec2.set(end1, -sj.length/2, 0); vec2.rotate(end1,end1,aj); add(end1,end1,xj); vec2.set(end2, sj.length/2, 0); vec2.rotate(end2,end2,aj); add(end2,end2,xj); circle.radius = sj.radius; // Do Narrowphase as two circles var numContacts1 = this.circlePlane(bj,circle,end1,0, bi,si,xi,ai, justTest), numContacts2 = this.circlePlane(bj,circle,end2,0, bi,si,xi,ai, justTest); if(justTest) return numContacts1 || numContacts2; else return numContacts1 + numContacts2; }; /** * @method capsulePlane * @deprecated Use .planeCapsule() instead! */ Narrowphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ console.warn("Narrowphase.prototype.capsulePlane() is deprecated. Use .planeCapsule() instead!"); return this.planeCapsule( bj,sj,xj,aj, bi,si,xi,ai, justTest ); } /** * Creates ContactEquations and FrictionEquations for a collision. * @method circlePlane * @param {Body} bi The first body that should be connected to the equations. * @param {Circle} si The circle shape participating in the collision. * @param {Array} xi Extra offset to take into account for the Shape, in addition to the one in circleBody.position. Will *not* be rotated by circleBody.angle (maybe it should, for sake of homogenity?). Set to null if none. * @param {Body} bj The second body that should be connected to the equations. * @param {Plane} sj The Plane shape that is participating * @param {Array} xj Extra offset for the plane shape. * @param {Number} aj Extra angle to apply to the plane */ Narrowphase.prototype[Shape.CIRCLE | Shape.PLANE] = Narrowphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ var circleBody = bi, circleShape = si, circleOffset = xi, // Offset from body center, rotated! planeBody = bj, shapeB = sj, planeOffset = xj, planeAngle = aj; planeAngle = planeAngle || 0; // Vector from plane to circle var planeToCircle = tmp1, worldNormal = tmp2, temp = tmp3; sub(planeToCircle, circleOffset, planeOffset); // World plane normal vec2.rotate(worldNormal, yAxis, planeAngle); // Normal direction distance var d = dot(worldNormal, planeToCircle); if(d > circleShape.radius) return 0; // No overlap. Abort. if(justTest) return true; // Create contact var contact = this.createContactEquation(planeBody,circleBody,sj,si); // ni is the plane world normal vec2.copy(contact.ni, worldNormal); // rj is the vector from circle center to the contact point vec2.scale(contact.rj, contact.ni, -circleShape.radius); add(contact.rj, contact.rj, circleOffset); sub(contact.rj, contact.rj, circleBody.position); // ri is the distance from plane center to contact. vec2.scale(temp, contact.ni, d); sub(contact.ri, planeToCircle, temp ); // Subtract normal distance vector from the distance vector add(contact.ri, contact.ri, planeOffset); sub(contact.ri, contact.ri, planeBody.position); this.contactEquations.push(contact); if(this.enableFriction){ this.frictionEquations.push( this.createFrictionFromContact(contact) ); } return 1; }; /** * Convex/convex Narrowphase.See this article for more info. * @method convexConvex * @param {Body} bi * @param {Convex} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Convex} sj * @param {Array} xj * @param {Number} aj */ Narrowphase.prototype[Shape.CONVEX] = Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest, precision ){ var sepAxis = tmp1, worldPoint = tmp2, worldPoint0 = tmp3, worldPoint1 = tmp4, worldEdge = tmp5, projected = tmp6, penetrationVec = tmp7, dist = tmp8, worldNormal = tmp9, numContacts = 0, precision = precision || 1e-10; var found = Narrowphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis); if(!found) return 0; // Make sure the separating axis is directed from shape i to shape j sub(dist,xj,xi); if(dot(sepAxis,dist) > 0){ vec2.scale(sepAxis,sepAxis,-1); } // Find edges with normals closest to the separating axis var closestEdge1 = Narrowphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis closestEdge2 = Narrowphase.getClosestEdge(sj,aj,sepAxis); if(closestEdge1==-1 || closestEdge2==-1) return 0; // Loop over the shapes for(var k=0; k<2; k++){ var closestEdgeA = closestEdge1, closestEdgeB = closestEdge2, shapeA = si, shapeB = sj, offsetA = xi, offsetB = xj, angleA = ai, angleB = aj, bodyA = bi, bodyB = bj; if(k==0){ // Swap! var tmp; tmp = closestEdgeA; closestEdgeA = closestEdgeB; closestEdgeB = tmp; tmp = shapeA; shapeA = shapeB; shapeB = tmp; tmp = offsetA; offsetA = offsetB; offsetB = tmp; tmp = angleA; angleA = angleB; angleB = tmp; tmp = bodyA; bodyA = bodyB; bodyB = tmp; } // Loop over 2 points in convex B for(var j=closestEdgeB; j max) max = value; if(min === null || value < min) min = value; } if(min > max){ var t = min; min = max; max = t; } // Project the position of the body onto the axis - need to add this to the result var offset = dot(convexOffset, worldAxis); vec2.set( result, min + offset, max + offset); }; // .findSeparatingAxis is called by other functions, need local tmp vectors var fsa_tmp1 = vec2.fromValues(0,0) , fsa_tmp2 = vec2.fromValues(0,0) , fsa_tmp3 = vec2.fromValues(0,0) , fsa_tmp4 = vec2.fromValues(0,0) , fsa_tmp5 = vec2.fromValues(0,0) , fsa_tmp6 = vec2.fromValues(0,0) /** * Find a separating axis between the shapes, that maximizes the separating distance between them. * @method findSeparatingAxis * @static * @param {Convex} c1 * @param {Array} offset1 * @param {Number} angle1 * @param {Convex} c2 * @param {Array} offset2 * @param {Number} angle2 * @param {Array} sepAxis The resulting axis * @return {Boolean} Whether the axis could be found. */ Narrowphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepAxis){ var maxDist = null, overlap = false, found = false, edge = fsa_tmp1, worldPoint0 = fsa_tmp2, worldPoint1 = fsa_tmp3, normal = fsa_tmp4, span1 = fsa_tmp5, span2 = fsa_tmp6; for(var j=0; j!==2; j++){ var c = c1, angle = angle1; if(j===1){ c = c2; angle = angle2; } for(var i=0; i!==c.vertices.length; i++){ // Get the world edge vec2.rotate(worldPoint0, c.vertices[i], angle); vec2.rotate(worldPoint1, c.vertices[(i+1)%c.vertices.length], angle); sub(edge, worldPoint1, worldPoint0); // Get normal - just rotate 90 degrees since vertices are given in CCW vec2.rotate(normal, edge, -Math.PI / 2); vec2.normalize(normal,normal); // Project hulls onto that normal Narrowphase.projectConvexOntoAxis(c1,offset1,angle1,normal,span1); Narrowphase.projectConvexOntoAxis(c2,offset2,angle2,normal,span2); // Order by span position var a=span1, b=span2, swapped = false; if(span1[0] > span2[0]){ b=span1; a=span2; swapped = true; } // Get separating distance var dist = b[0] - a[1]; overlap = dist < 0; if(maxDist===null || dist > maxDist){ vec2.copy(sepAxis, normal); maxDist = dist; found = overlap; } } } return found; }; // .getClosestEdge is called by other functions, need local tmp vectors var gce_tmp1 = vec2.fromValues(0,0) , gce_tmp2 = vec2.fromValues(0,0) , gce_tmp3 = vec2.fromValues(0,0) /** * Get the edge that has a normal closest to an axis. * @method getClosestEdge * @static * @param {Convex} c * @param {Number} angle * @param {Array} axis * @param {Boolean} flip * @return {Number} Index of the edge that is closest. This index and the next spans the resulting edge. Returns -1 if failed. */ Narrowphase.getClosestEdge = function(c,angle,axis,flip){ var localAxis = gce_tmp1, edge = gce_tmp2, normal = gce_tmp3; // Convert the axis to local coords of the body vec2.rotate(localAxis, axis, -angle); if(flip){ vec2.scale(localAxis,localAxis,-1); } var closestEdge = -1, N = c.vertices.length, halfPi = Math.PI / 2; for(var i=0; i!==N; i++){ // Get the edge sub(edge, c.vertices[(i+1)%N], c.vertices[i%N]); // Get normal - just rotate 90 degrees since vertices are given in CCW vec2.rotate(normal, edge, -halfPi); vec2.normalize(normal,normal); var d = dot(normal,localAxis); if(closestEdge == -1 || d > maxDot){ closestEdge = i % N; maxDot = d; } } return closestEdge; }; },{"../equations/ContactEquation":23,"../equations/FrictionEquation":25,"../math/vec2":33,"../objects/Body":34,"../shapes/Circle":38,"../shapes/Shape":44,"../utils/Utils":49}],14:[function(require,module,exports){ var Plane = require("../shapes/Plane"); var Broadphase = require("../collision/Broadphase"); module.exports = { QuadTree : QuadTree, Node : Node, BoundsNode : BoundsNode, }; /** * QuadTree data structure. See https://github.com/mikechambers/ExamplesByMesh/tree/master/JavaScript/QuadTree * @class QuadTree * @constructor * @param {Object} An object representing the bounds of the top level of the QuadTree. The object * should contain the following properties : x, y, width, height * @param {Boolean} pointQuad Whether the QuadTree will contain points (true), or items with bounds * (width / height)(false). Default value is false. * @param {Number} maxDepth The maximum number of levels that the quadtree will create. Default is 4. * @param {Number} maxChildren The maximum number of children that a node can contain before it is split into sub-nodes. */ function QuadTree(bounds, pointQuad, maxDepth, maxChildren){ var node; if(pointQuad){ node = new Node(bounds, 0, maxDepth, maxChildren); } else { node = new BoundsNode(bounds, 0, maxDepth, maxChildren); } /** * The root node of the QuadTree which covers the entire area being segmented. * @property root * @type Node */ this.root = node; } /** * Inserts an item into the QuadTree. * @method insert * @param {Object|Array} item The item or Array of items to be inserted into the QuadTree. The item should expose x, y * properties that represents its position in 2D space. */ QuadTree.prototype.insert = function(item){ if(item instanceof Array){ var len = item.length; for(var i = 0; i < len; i++){ this.root.insert(item[i]); } } else { this.root.insert(item); } } /** * Clears all nodes and children from the QuadTree * @method clear */ QuadTree.prototype.clear = function(){ this.root.clear(); } /** * Retrieves all items / points in the same node as the specified item / point. If the specified item * overlaps the bounds of a node, then all children in both nodes will be returned. * @method retrieve * @param {Object} item An object representing a 2D coordinate point (with x, y properties), or a shape * with dimensions (x, y, width, height) properties. */ QuadTree.prototype.retrieve = function(item){ //get a copy of the array of items var out = this.root.retrieve(item).slice(0); return out; } QuadTree.prototype.getCollisionPairs = function(world){ var result = []; // Add all bodies this.insert(world.bodies); /* console.log("bodies",world.bodies.length); console.log("maxDepth",this.root.maxDepth,"maxChildren",this.root.maxChildren); */ for(var i=0; i!==world.bodies.length; i++){ var b = world.bodies[i], items = this.retrieve(b); //console.log("items",items.length); // Check results for(var j=0, len=items.length; j!==len; j++){ var item = items[j]; if(b === item) continue; // Do not add self // Check if they were already added var found = false; for(var k=0, numAdded=result.length; k= this.maxDepth) && len > this.maxChildren) { this.subdivide(); for(var i = 0; i < len; i++){ this.insert(this.children[i]); } this.children.length = 0; } } Node.prototype.retrieve = function(item){ if(this.nodes.length){ var index = this.findIndex(item); return this.nodes[index].retrieve(item); } return this.children; } Node.prototype.findIndex = function(item){ var b = this.bounds; var left = (item.position[0]-item.boundingRadius > b.x + b.width / 2) ? false : true; var top = (item.position[1]-item.boundingRadius > b.y + b.height / 2) ? false : true; if(item instanceof Plane){ left = top = false; // Will overlap the left/top boundary since it is infinite } //top left var index = Node.TOP_LEFT; if(left){ if(!top){ index = Node.BOTTOM_LEFT; } } else { if(top){ index = Node.TOP_RIGHT; } else { index = Node.BOTTOM_RIGHT; } } return index; } Node.prototype.subdivide = function(){ var depth = this.depth + 1; var bx = this.bounds.x; var by = this.bounds.y; //floor the values var b_w_h = (this.bounds.width / 2); var b_h_h = (this.bounds.height / 2); var bx_b_w_h = bx + b_w_h; var by_b_h_h = by + b_h_h; //top left this.nodes[Node.TOP_LEFT] = new this.classConstructor({ x:bx, y:by, width:b_w_h, height:b_h_h }, depth); //top right this.nodes[Node.TOP_RIGHT] = new this.classConstructor({ x:bx_b_w_h, y:by, width:b_w_h, height:b_h_h }, depth); //bottom left this.nodes[Node.BOTTOM_LEFT] = new this.classConstructor({ x:bx, y:by_b_h_h, width:b_w_h, height:b_h_h }, depth); //bottom right this.nodes[Node.BOTTOM_RIGHT] = new this.classConstructor({ x:bx_b_w_h, y:by_b_h_h, width:b_w_h, height:b_h_h }, depth); } Node.prototype.clear = function(){ this.children.length = 0; var len = this.nodes.length; for(var i = 0; i < len; i++){ this.nodes[i].clear(); } this.nodes.length = 0; } // BoundsQuadTree function BoundsNode(bounds, depth, maxChildren, maxDepth){ Node.call(this, bounds, depth, maxChildren, maxDepth); this.stuckChildren = []; } BoundsNode.prototype = new Node(); BoundsNode.prototype.classConstructor = BoundsNode; BoundsNode.prototype.stuckChildren = null; //we use this to collect and conctenate items being retrieved. This way //we dont have to continuously create new Array instances. //Note, when returned from QuadTree.retrieve, we then copy the array BoundsNode.prototype.out = []; BoundsNode.prototype.insert = function(item){ if(this.nodes.length){ var index = this.findIndex(item); var node = this.nodes[index]; /* console.log("radius:",item.boundingRadius); console.log("item x:",item.position[0] - item.boundingRadius,"x range:",node.bounds.x,node.bounds.x+node.bounds.width); console.log("item y:",item.position[1] - item.boundingRadius,"y range:",node.bounds.y,node.bounds.y+node.bounds.height); */ //todo: make _bounds bounds if( !(item instanceof Plane) && // Plane is infinite.. Make it a "stuck" child item.position[0] - item.boundingRadius >= node.bounds.x && item.position[0] + item.boundingRadius <= node.bounds.x + node.bounds.width && item.position[1] - item.boundingRadius >= node.bounds.y && item.position[1] + item.boundingRadius <= node.bounds.y + node.bounds.height){ this.nodes[index].insert(item); } else { this.stuckChildren.push(item); } return; } this.children.push(item); var len = this.children.length; if(this.depth < this.maxDepth && len > this.maxChildren){ this.subdivide(); for(var i=0; i