diff --git a/build/config.php b/build/config.php index bd122201a..b192f1372 100644 --- a/build/config.php +++ b/build/config.php @@ -48,11 +48,16 @@ + + + + */ echo << @@ -173,9 +178,8 @@ - - - + + diff --git a/build/p2.js b/build/p2.js new file mode 100644 index 000000000..6abd4da40 --- /dev/null +++ b/build/p2.js @@ -0,0 +1,10013 @@ +/** + * 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. + * @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 + */ +Narrowphase.prototype.reset = function(world){ + + // Emit world separation event + if(world && world.emitSeparationEvent){ + for(var i=0; i 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); + + 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; + 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){ + // TODO +}; + +/** + * 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){ + // TODO +}; + +/** + * 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){ + // TODO +}; + +/** + * 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){ + // TODO +}; + +/** + * 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){ + // TODO +}; + +/** + * 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){ + // TODO +}; + +/** + * 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){ + // TODO +}; + +/** + * 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){ + var worldVertex0 = tmp1, + worldVertex1 = tmp2, + worldVertex01 = tmp3, + worldVertex11 = tmp4, + worldEdge = tmp5, + worldEdgeUnit = tmp6, + dist = tmp7, + worldNormal = tmp8, + worldTangent = tmp9, + verts = tmpArray; + + // 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 true; + } + } + + // 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 false; + } + + 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 true; +}; + +/** + * 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 ){ + 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 > 0; +}; + +/** + * @method convexPlane + * @deprecated Use .planeConvex() instead! + */ +Narrowphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ + console.warn("Narrowphase.prototype.convexPlane is deprecated. Use planeConvex instead!"); + return this.planeConvex( bj,sj,xj,aj, bi,si,xi,ai ); +} + +/** + * 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 false; + 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 true; +}; + +/** + * 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 false; + 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 true; +}; + +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 ){ + 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 + this.circlePlane(bj,circle,end1,0, bi,si,xi,ai); + this.circlePlane(bj,circle,end2,0, bi,si,xi,ai); +}; + +/** + * @method capsulePlane + * @deprecated Use .planeCapsule() instead! + */ +Narrowphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ + console.warn("Narrowphase.prototype.capsulePlane() is deprecated. Use .planeCapsule() instead!"); + return this.planeCapsule( bj,sj,xj,aj, bi,si,xi,ai ); +} + +/** + * 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 ){ + 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 false; // No overlap. Abort. + + // 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 true; +}; + + +/** + * 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, precision ){ + var sepAxis = tmp1, + worldPoint = tmp2, + worldPoint0 = tmp3, + worldPoint1 = tmp4, + worldEdge = tmp5, + projected = tmp6, + penetrationVec = tmp7, + dist = tmp8, + worldNormal = tmp9, + precision = precision || 1e-10; + + var found = Narrowphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis); + if(!found) return false; + + // 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 false; + + // 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