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; ithis tutorial.
+ *
+ * @class PrismaticConstraint
+ * @constructor
+ * @extends {Constraint}
+ * @author schteppe
+ * @param {Body} bodyA
+ * @param {Body} bodyB
+ * @param {Object} options
+ * @param {Number} options.maxForce Max force to be applied by the constraint
+ * @param {Array} options.localAnchorA Body A's anchor point, defined in its own local frame.
+ * @param {Array} options.localAnchorB Body B's anchor point, defined in its own local frame.
+ * @param {Array} options.localAxisA An axis, defined in body A frame, that body B's anchor point may slide along.
+ * @param {Boolean} options.disableRotationalLock If set to true, bodyB will be free to rotate around its anchor point.
+ */
+function PrismaticConstraint(bodyA,bodyB,options){
+ options = options || {};
+ Constraint.call(this,bodyA,bodyB);
+
+ // Get anchors
+ var localAnchorA = vec2.fromValues(0,0),
+ localAxisA = vec2.fromValues(1,0),
+ localAnchorB = vec2.fromValues(0,0);
+ if(options.localAnchorA) vec2.copy(localAnchorA, options.localAnchorA);
+ if(options.localAxisA) vec2.copy(localAxisA, options.localAxisA);
+ if(options.localAnchorB) vec2.copy(localAnchorB, options.localAnchorB);
+
+ /**
+ * @property localAnchorA
+ * @type {Array}
+ */
+ this.localAnchorA = localAnchorA;
+
+ /**
+ * @property localAnchorB
+ * @type {Array}
+ */
+ this.localAnchorB = localAnchorB;
+
+ /**
+ * @property localAxisA
+ * @type {Array}
+ */
+ this.localAxisA = localAxisA;
+
+ /*
+
+ The constraint violation for the common axis point is
+
+ g = ( xj + rj - xi - ri ) * t := gg*t
+
+ where r are body-local anchor points, and t is a tangent to the constraint axis defined in body i frame.
+
+ gdot = ( vj + wj x rj - vi - wi x ri ) * t + ( xj + rj - xi - ri ) * ( wi x t )
+
+ Note the use of the chain rule. Now we identify the jacobian
+
+ G*W = [ -t -ri x t + t x gg t rj x t ] * [vi wi vj wj]
+
+ The rotational part is just a rotation lock.
+
+ */
+
+ var maxForce = this.maxForce = typeof(options.maxForce)!="undefined" ? options.maxForce : Number.MAX_VALUE;
+
+ // Translational part
+ var trans = new Equation(bodyA,bodyB,-maxForce,maxForce);
+ var ri = new vec2.create(),
+ rj = new vec2.create(),
+ gg = new vec2.create(),
+ t = new vec2.create();
+ trans.computeGq = function(){
+ // g = ( xj + rj - xi - ri ) * t
+ return vec2.dot(gg,t);
+ };
+ trans.update = function(){
+ var G = this.G,
+ xi = bodyA.position,
+ xj = bodyB.position;
+ vec2.rotate(ri,localAnchorA,bodyA.angle);
+ vec2.rotate(rj,localAnchorB,bodyB.angle);
+ vec2.add(gg,xj,rj);
+ vec2.sub(gg,gg,xi);
+ vec2.sub(gg,gg,ri);
+ vec2.rotate(t,localAxisA,bodyA.angle+Math.PI/2);
+
+ G[0] = -t[0];
+ G[1] = -t[1];
+ G[2] = -vec2.crossLength(ri,t) + vec2.crossLength(t,gg);
+ G[3] = t[0];
+ G[4] = t[1];
+ G[5] = vec2.crossLength(rj,t);
+ }
+ this.equations.push(trans);
+
+ // Rotational part
+ if(!options.disableRotationalLock){
+ var rot = new RotationalLockEquation(bodyA,bodyB,-maxForce,maxForce);
+ this.equations.push(rot);
+ }
+
+ /**
+ * The position of anchor A relative to anchor B, along the constraint axis.
+ * @property position
+ * @type {Number}
+ */
+ this.position = 0;
+
+ this.velocity = 0;
+
+ /**
+ * Set to true to enable lower limit.
+ * @property lowerLimitEnabled
+ * @type {Boolean}
+ */
+ this.lowerLimitEnabled = false;
+
+ /**
+ * Set to true to enable upper limit.
+ * @property upperLimitEnabled
+ * @type {Boolean}
+ */
+ this.upperLimitEnabled = false;
+
+ /**
+ * Lower constraint limit. The constraint position is forced to be larger than this value.
+ * @property lowerLimit
+ * @type {Number}
+ */
+ this.lowerLimit = 0;
+
+ /**
+ * Upper constraint limit. The constraint position is forced to be smaller than this value.
+ * @property upperLimit
+ * @type {Number}
+ */
+ this.upperLimit = 1;
+
+ // Equations used for limits
+ this.upperLimitEquation = new ContactEquation(bodyA,bodyB);
+ this.lowerLimitEquation = new ContactEquation(bodyA,bodyB);
+
+ // Set max/min forces
+ this.upperLimitEquation.minForce = this.lowerLimitEquation.minForce = 0;
+ this.upperLimitEquation.maxForce = this.lowerLimitEquation.maxForce = maxForce;
+
+ /**
+ * Equation used for the motor.
+ * @property motorEquation
+ * @type {Equation}
+ */
+ this.motorEquation = new Equation(bodyA,bodyB);
+
+ /**
+ * The current motor state. Enable or disable the motor using .enableMotor
+ * @property motorEnabled
+ * @type {Boolean}
+ */
+ this.motorEnabled = false;
+
+ /**
+ * Set the target speed for the motor.
+ * @property motorSpeed
+ * @type {Number}
+ */
+ this.motorSpeed = 0;
+
+ var that = this;
+ var motorEquation = this.motorEquation;
+ var old = motorEquation.computeGW;
+ motorEquation.computeGq = function(){ return 0; };
+ motorEquation.computeGW = function(){
+ var G = this.G,
+ bi = this.bi,
+ bj = this.bj,
+ vi = bi.velocity,
+ vj = bj.velocity,
+ wi = bi.angularVelocity,
+ wj = bj.angularVelocity;
+ return this.transformedGmult(G,vi,wi,vj,wj) + that.motorSpeed;
+ };
+}
+
+PrismaticConstraint.prototype = new Constraint();
+
+var worldAxisA = vec2.create(),
+ worldAnchorA = vec2.create(),
+ worldAnchorB = vec2.create(),
+ orientedAnchorA = vec2.create(),
+ orientedAnchorB = vec2.create(),
+ tmp = vec2.create();
+
+/**
+ * Update the constraint equations. Should be done if any of the bodies changed position, before solving.
+ * @method update
+ */
+PrismaticConstraint.prototype.update = function(){
+ var eqs = this.equations,
+ trans = eqs[0],
+ upperLimit = this.upperLimit,
+ lowerLimit = this.lowerLimit,
+ upperLimitEquation = this.upperLimitEquation,
+ lowerLimitEquation = this.lowerLimitEquation,
+ bodyA = this.bodyA,
+ bodyB = this.bodyB,
+ localAxisA = this.localAxisA,
+ localAnchorA = this.localAnchorA,
+ localAnchorB = this.localAnchorB;
+
+ trans.update();
+
+ // Transform local things to world
+ vec2.rotate(worldAxisA, localAxisA, bodyA.angle);
+ vec2.rotate(orientedAnchorA, localAnchorA, bodyA.angle);
+ vec2.add(worldAnchorA, orientedAnchorA, bodyA.position);
+ vec2.rotate(orientedAnchorB, localAnchorB, bodyB.angle);
+ vec2.add(worldAnchorB, orientedAnchorB, bodyB.position);
+
+ var relPosition = this.position = vec2.dot(worldAnchorB,worldAxisA) - vec2.dot(worldAnchorA,worldAxisA);
+
+ // Motor
+ if(this.motorEnabled){
+ // G = [ a a x ri -a -a x rj ]
+ var G = this.motorEquation.G;
+ G[0] = worldAxisA[0];
+ G[1] = worldAxisA[1];
+ G[2] = vec2.crossLength(worldAxisA,orientedAnchorB);
+ G[3] = -worldAxisA[0];
+ G[4] = -worldAxisA[1];
+ G[5] = -vec2.crossLength(worldAxisA,orientedAnchorA);
+ }
+
+ /*
+ Limits strategy:
+ Add contact equation, with normal along the constraint axis.
+ min/maxForce is set so the constraint is repulsive in the correct direction.
+ Some offset is added to either equation.ri or .rj to get the correct upper/lower limit.
+
+ ^
+ |
+ upperLimit x
+ | ------
+ anchorB x<---| B |
+ | | |
+ ------ | ------
+ | | |
+ | A |-->x anchorA
+ ------ |
+ x lowerLimit
+ |
+ axis
+ */
+
+ if(this.upperLimitEnabled && relPosition > upperLimit){
+ // Update contact constraint normal, etc
+ vec2.scale(upperLimitEquation.ni, worldAxisA, -1);
+ vec2.sub(upperLimitEquation.ri, worldAnchorA, bodyA.position);
+ vec2.sub(upperLimitEquation.rj, worldAnchorB, bodyB.position);
+ vec2.scale(tmp,worldAxisA,upperLimit);
+ vec2.add(upperLimitEquation.ri,upperLimitEquation.ri,tmp);
+ if(eqs.indexOf(upperLimitEquation)==-1)
+ eqs.push(upperLimitEquation);
+ } else {
+ var idx = eqs.indexOf(upperLimitEquation);
+ if(idx != -1) eqs.splice(idx,1);
+ }
+
+ if(this.lowerLimitEnabled && relPosition < lowerLimit){
+ // Update contact constraint normal, etc
+ vec2.scale(lowerLimitEquation.ni, worldAxisA, 1);
+ vec2.sub(lowerLimitEquation.ri, worldAnchorA, bodyA.position);
+ vec2.sub(lowerLimitEquation.rj, worldAnchorB, bodyB.position);
+ vec2.scale(tmp,worldAxisA,lowerLimit);
+ vec2.sub(lowerLimitEquation.rj,lowerLimitEquation.rj,tmp);
+ if(eqs.indexOf(lowerLimitEquation)==-1)
+ eqs.push(lowerLimitEquation);
+ } else {
+ var idx = eqs.indexOf(lowerLimitEquation);
+ if(idx != -1) eqs.splice(idx,1);
+ }
+};
+
+/**
+ * Enable the motor
+ * @method enableMotor
+ */
+PrismaticConstraint.prototype.enableMotor = function(){
+ if(this.motorEnabled) return;
+ this.equations.push(this.motorEquation);
+ this.motorEnabled = true;
+};
+
+/**
+ * Disable the rotational motor
+ * @method disableMotor
+ */
+PrismaticConstraint.prototype.disableMotor = function(){
+ if(!this.motorEnabled) return;
+ var i = this.equations.indexOf(this.motorEquation);
+ this.equations.splice(i,1);
+ this.motorEnabled = false;
+};
+
+},{"../equations/ContactEquation":23,"../equations/Equation":24,"../equations/RotationalLockEquation":26,"../math/vec2":33,"./Constraint":16}],21:[function(require,module,exports){
+var Constraint = require('./Constraint')
+, Equation = require('../equations/Equation')
+, RotationalVelocityEquation = require('../equations/RotationalVelocityEquation')
+, RotationalLockEquation = require('../equations/RotationalLockEquation')
+, vec2 = require('../math/vec2')
+
+module.exports = RevoluteConstraint;
+
+var worldPivotA = vec2.create(),
+ worldPivotB = vec2.create(),
+ xAxis = vec2.fromValues(1,0),
+ yAxis = vec2.fromValues(0,1),
+ g = vec2.create();
+
+/**
+ * Connects two bodies at given offset points, letting them rotate relative to each other around this point.
+ * @class RevoluteConstraint
+ * @constructor
+ * @author schteppe
+ * @param {Body} bodyA
+ * @param {Float32Array} pivotA The point relative to the center of mass of bodyA which bodyA is constrained to.
+ * @param {Body} bodyB Body that will be constrained in a similar way to the same point as bodyA. We will therefore get sort of a link between bodyA and bodyB. If not specified, bodyA will be constrained to a static point.
+ * @param {Float32Array} pivotB See pivotA.
+ * @param {Number} maxForce The maximum force that should be applied to constrain the bodies.
+ * @extends {Constraint}
+ * @todo Ability to specify world points
+ */
+function RevoluteConstraint(bodyA, pivotA, bodyB, pivotB, maxForce){
+ Constraint.call(this,bodyA,bodyB);
+
+ maxForce = this.maxForce = typeof(maxForce)!="undefined" ? maxForce : Number.MAX_VALUE;
+
+ this.pivotA = pivotA;
+ this.pivotB = pivotB;
+
+ // Equations to be fed to the solver
+ var eqs = this.equations = [
+ new Equation(bodyA,bodyB,-maxForce,maxForce),
+ new Equation(bodyA,bodyB,-maxForce,maxForce),
+ ];
+
+ var x = eqs[0];
+ var y = eqs[1];
+
+ x.computeGq = function(){
+ vec2.rotate(worldPivotA, pivotA, bodyA.angle);
+ vec2.rotate(worldPivotB, pivotB, bodyB.angle);
+ vec2.add(g, bodyB.position, worldPivotB);
+ vec2.sub(g, g, bodyA.position);
+ vec2.sub(g, g, worldPivotA);
+ return vec2.dot(g,xAxis);
+ };
+
+ y.computeGq = function(){
+ vec2.rotate(worldPivotA, pivotA, bodyA.angle);
+ vec2.rotate(worldPivotB, pivotB, bodyB.angle);
+ vec2.add(g, bodyB.position, worldPivotB);
+ vec2.sub(g, g, bodyA.position);
+ vec2.sub(g, g, worldPivotA);
+ return vec2.dot(g,yAxis);
+ };
+
+ y.minForce = x.minForce = -maxForce;
+ y.maxForce = x.maxForce = maxForce;
+
+ this.motorEquation = new RotationalVelocityEquation(bodyA,bodyB);
+ this.motorEnabled = false;
+
+ /**
+ * The constraint position
+ * @property angle
+ * @type {Number}
+ */
+ this.angle = 0;
+
+ /**
+ * Set to true to enable lower limit
+ * @property lowerLimitEnabled
+ * @type {Boolean}
+ */
+ this.lowerLimitEnabled = false;
+
+ /**
+ * Set to true to enable upper limit
+ * @property upperLimitEnabled
+ * @type {Boolean}
+ */
+ this.upperLimitEnabled = false;
+
+ /**
+ * The lower limit on the constraint angle.
+ * @property lowerLimit
+ * @type {Boolean}
+ */
+ this.lowerLimit = 0;
+
+ /**
+ * The upper limit on the constraint angle.
+ * @property upperLimit
+ * @type {Boolean}
+ */
+ this.upperLimit = 0;
+
+ this.upperLimitEquation = new RotationalLockEquation(bodyA,bodyB);
+ this.lowerLimitEquation = new RotationalLockEquation(bodyA,bodyB);
+ this.upperLimitEquation.minForce = 0;
+ this.lowerLimitEquation.maxForce = 0;
+}
+RevoluteConstraint.prototype = new Constraint();
+
+RevoluteConstraint.prototype.update = function(){
+ var bodyA = this.bodyA,
+ bodyB = this.bodyB,
+ pivotA = this.pivotA,
+ pivotB = this.pivotB,
+ eqs = this.equations,
+ normal = eqs[0],
+ tangent= eqs[1],
+ x = eqs[0],
+ y = eqs[1],
+ upperLimit = this.upperLimit,
+ lowerLimit = this.lowerLimit,
+ upperLimitEquation = this.upperLimitEquation,
+ lowerLimitEquation = this.lowerLimitEquation;
+
+ var relAngle = this.angle = bodyB.angle - bodyA.angle;
+
+ if(this.upperLimitEnabled && relAngle > upperLimit){
+ upperLimitEquation.angle = upperLimit;
+ if(eqs.indexOf(upperLimitEquation)==-1)
+ eqs.push(upperLimitEquation);
+ } else {
+ var idx = eqs.indexOf(upperLimitEquation);
+ if(idx != -1) eqs.splice(idx,1);
+ }
+
+ if(this.lowerLimitEnabled && relAngle < lowerLimit){
+ lowerLimitEquation.angle = lowerLimit;
+ if(eqs.indexOf(lowerLimitEquation)==-1)
+ eqs.push(lowerLimitEquation);
+ } else {
+ var idx = eqs.indexOf(lowerLimitEquation);
+ if(idx != -1) eqs.splice(idx,1);
+ }
+
+ /*
+
+ The constraint violation is
+
+ g = xj + rj - xi - ri
+
+ ...where xi and xj are the body positions and ri and rj world-oriented offset vectors. Differentiate:
+
+ gdot = vj + wj x rj - vi - wi x ri
+
+ We split this into x and y directions. (let x and y be unit vectors along the respective axes)
+
+ gdot * x = ( vj + wj x rj - vi - wi x ri ) * x
+ = ( vj*x + (wj x rj)*x -vi*x -(wi x ri)*x
+ = ( vj*x + (rj x x)*wj -vi*x -(ri x x)*wi
+ = [ -x -(ri x x) x (rj x x)] * [vi wi vj wj]
+ = G*W
+
+ ...and similar for y. We have then identified the jacobian entries for x and y directions:
+
+ Gx = [ x (rj x x) -x -(ri x x)]
+ Gy = [ y (rj x y) -y -(ri x y)]
+
+ */
+
+ vec2.rotate(worldPivotA, pivotA, bodyA.angle);
+ vec2.rotate(worldPivotB, pivotB, bodyB.angle);
+
+ // todo: these are a bit sparse. We could save some computations on making custom eq.computeGW functions, etc
+
+ x.G[0] = -1;
+ x.G[1] = 0;
+ x.G[2] = -vec2.crossLength(worldPivotA,xAxis);
+ x.G[3] = 1;
+ x.G[4] = 0;
+ x.G[5] = vec2.crossLength(worldPivotB,xAxis);
+
+ y.G[0] = 0;
+ y.G[1] = -1;
+ y.G[2] = -vec2.crossLength(worldPivotA,yAxis);
+ y.G[3] = 0;
+ y.G[4] = 1;
+ y.G[5] = vec2.crossLength(worldPivotB,yAxis);
+};
+
+/**
+ * Enable the rotational motor
+ * @method enableMotor
+ */
+RevoluteConstraint.prototype.enableMotor = function(){
+ if(this.motorEnabled) return;
+ this.equations.push(this.motorEquation);
+ this.motorEnabled = true;
+};
+
+/**
+ * Disable the rotational motor
+ * @method disableMotor
+ */
+RevoluteConstraint.prototype.disableMotor = function(){
+ if(!this.motorEnabled) return;
+ var i = this.equations.indexOf(this.motorEquation);
+ this.equations.splice(i,1);
+ this.motorEnabled = false;
+};
+
+/**
+ * Check if the motor is enabled.
+ * @method motorIsEnabled
+ * @return {Boolean}
+ */
+RevoluteConstraint.prototype.motorIsEnabled = function(){
+ return !!this.motorEnabled;
+};
+
+/**
+ * Set the speed of the rotational constraint motor
+ * @method setMotorSpeed
+ * @param {Number} speed
+ */
+RevoluteConstraint.prototype.setMotorSpeed = function(speed){
+ if(!this.motorEnabled) return;
+ var i = this.equations.indexOf(this.motorEquation);
+ this.equations[i].relativeVelocity = speed;
+};
+
+/**
+ * Get the speed of the rotational constraint motor
+ * @method getMotorSpeed
+ * @return {Number} The current speed, or false if the motor is not enabled.
+ */
+RevoluteConstraint.prototype.getMotorSpeed = function(){
+ if(!this.motorEnabled) return false;
+ return this.motorEquation.relativeVelocity;
+};
+
+},{"../equations/Equation":24,"../equations/RotationalLockEquation":26,"../equations/RotationalVelocityEquation":27,"../math/vec2":33,"./Constraint":16}],22:[function(require,module,exports){
+var Equation = require("./Equation"),
+ vec2 = require('../math/vec2');
+
+module.exports = AngleLockEquation;
+
+/**
+ * Locks the relative angle between two bodies. The constraint tries to keep the dot product between two vectors, local in each body, to zero. The local angle in body i is a parameter.
+ *
+ * @class AngleLockEquation
+ * @constructor
+ * @extends Equation
+ * @param {Body} bi
+ * @param {Body} bj
+ * @param {Object} options
+ * @param {Number} options.angle Angle to add to the local vector in body i.
+ * @param {Number} options.ratio Gear ratio
+ */
+function AngleLockEquation(bi,bj,options){
+ options = options || {};
+ Equation.call(this,bi,bj,-Number.MAX_VALUE,Number.MAX_VALUE);
+ this.angle = options.angle || 0;
+ this.ratio = typeof(options.ratio)=="number" ? options.ratio : 1;
+ this.setRatio(this.ratio);
+};
+AngleLockEquation.prototype = new Equation();
+AngleLockEquation.prototype.constructor = AngleLockEquation;
+
+AngleLockEquation.prototype.computeGq = function(){
+ return this.ratio*this.bi.angle - this.bj.angle + this.angle;
+};
+
+AngleLockEquation.prototype.setRatio = function(ratio){
+ var G = this.G;
+ G[2] = ratio;
+ G[5] = -1;
+ this.ratio = ratio;
+};
+
+},{"../math/vec2":33,"./Equation":24}],23:[function(require,module,exports){
+var Equation = require("./Equation"),
+ vec2 = require('../math/vec2'),
+ mat2 = require('../math/mat2');
+
+module.exports = ContactEquation;
+
+/**
+ * Non-penetration constraint equation. Tries to make the ri and rj vectors the same point.
+ *
+ * @class ContactEquation
+ * @constructor
+ * @extends Equation
+ * @param {Body} bi
+ * @param {Body} bj
+ */
+function ContactEquation(bi,bj){
+ Equation.call(this,bi,bj,0,Number.MAX_VALUE);
+
+ /**
+ * Vector from body i center of mass to the contact point.
+ * @property ri
+ * @type {Array}
+ */
+ this.ri = vec2.create();
+ this.penetrationVec = vec2.create();
+
+ /**
+ * Vector from body j center of mass to the contact point.
+ * @property rj
+ * @type {Array}
+ */
+ this.rj = vec2.create();
+
+ /**
+ * The normal vector, pointing out of body i
+ * @property ni
+ * @type {Array}
+ */
+ this.ni = vec2.create();
+
+ /**
+ * The restitution to use. 0=no bounciness, 1=max bounciness.
+ * @property restitution
+ * @type {Number}
+ */
+ this.restitution = 0;
+
+ /**
+ * Set to true if this is the first impact between the bodies (not persistant contact).
+ * @property firstImpact
+ * @type {Boolean}
+ */
+ this.firstImpact = false;
+
+ /**
+ * The shape in body i that triggered this contact.
+ * @property shapeA
+ * @type {Shape}
+ */
+ this.shapeA = null;
+
+ /**
+ * The shape in body j that triggered this contact.
+ * @property shapeB
+ * @type {Shape}
+ */
+ this.shapeB = null;
+};
+ContactEquation.prototype = new Equation();
+ContactEquation.prototype.constructor = ContactEquation;
+ContactEquation.prototype.computeB = function(a,b,h){
+ var bi = this.bi,
+ bj = this.bj,
+ ri = this.ri,
+ rj = this.rj,
+ xi = bi.position,
+ xj = bj.position;
+
+ var penetrationVec = this.penetrationVec,
+ n = this.ni,
+ G = this.G;
+
+ // Caluclate cross products
+ var rixn = vec2.crossLength(ri,n),
+ rjxn = vec2.crossLength(rj,n);
+
+ // G = [-n -rixn n rjxn]
+ G[0] = -n[0];
+ G[1] = -n[1];
+ G[2] = -rixn;
+ G[3] = n[0];
+ G[4] = n[1];
+ G[5] = rjxn;
+
+ // Calculate q = xj+rj -(xi+ri) i.e. the penetration vector
+ vec2.add(penetrationVec,xj,rj);
+ vec2.sub(penetrationVec,penetrationVec,xi);
+ vec2.sub(penetrationVec,penetrationVec,ri);
+
+ // Compute iteration
+ var GW, Gq;
+ if(this.firstImpact && this.restitution !== 0){
+ Gq = 0;
+ GW = (1/b)*(1+this.restitution) * this.computeGW();
+ } else {
+ GW = this.computeGW();
+ Gq = vec2.dot(n,penetrationVec);
+ }
+
+ var GiMf = this.computeGiMf();
+ var B = - Gq * a - GW * b - h*GiMf;
+
+ return B;
+};
+
+},{"../math/mat2":31,"../math/vec2":33,"./Equation":24}],24:[function(require,module,exports){
+module.exports = Equation;
+
+var vec2 = require('../math/vec2'),
+ mat2 = require('../math/mat2'),
+ Utils = require('../utils/Utils');
+
+/**
+ * Base class for constraint equations.
+ * @class Equation
+ * @constructor
+ * @param {Body} bi First body participating in the equation
+ * @param {Body} bj Second body participating in the equation
+ * @param {number} minForce Minimum force to apply. Default: -1e6
+ * @param {number} maxForce Maximum force to apply. Default: 1e6
+ */
+function Equation(bi,bj,minForce,maxForce){
+
+ /**
+ * Minimum force to apply when solving
+ * @property minForce
+ * @type {Number}
+ */
+ this.minForce = typeof(minForce)=="undefined" ? -1e6 : minForce;
+
+ /**
+ * Max force to apply when solving
+ * @property maxForce
+ * @type {Number}
+ */
+ this.maxForce = typeof(maxForce)=="undefined" ? 1e6 : maxForce;
+
+ /**
+ * First body participating in the constraint
+ * @property bi
+ * @type {Body}
+ */
+ this.bi = bi;
+
+ /**
+ * Second body participating in the constraint
+ * @property bj
+ * @type {Body}
+ */
+ this.bj = bj;
+
+ /**
+ * The stiffness of this equation. Typically chosen to a large number (~1e7), but can be chosen somewhat freely to get a stable simulation.
+ * @property stiffness
+ * @type {Number}
+ */
+ this.stiffness = 1e6;
+
+ /**
+ * The number of time steps needed to stabilize the constraint equation. Typically between 3 and 5 time steps.
+ * @property relaxation
+ * @type {Number}
+ */
+ this.relaxation = 4;
+
+ /**
+ * The Jacobian entry of this equation. 6 numbers, 3 per body (x,y,angle).
+ * @property G
+ * @type {Array}
+ */
+ this.G = new Utils.ARRAY_TYPE(6);
+
+ // Constraint frames for body i and j
+ /*
+ this.xi = vec2.create();
+ this.xj = vec2.create();
+ this.ai = 0;
+ this.aj = 0;
+ */
+ this.offset = 0;
+
+ this.a = 0;
+ this.b = 0;
+ this.eps = 0;
+ this.h = 0;
+ this.updateSpookParams(1/60);
+
+ /**
+ * The resulting constraint multiplier from the last solve. This is mostly equivalent to the force produced by the constraint.
+ * @property multiplier
+ * @type {Number}
+ */
+ this.multiplier = 0;
+};
+Equation.prototype.constructor = Equation;
+
+/**
+ * Update SPOOK parameters .a, .b and .eps according to the given time step. See equations 9, 10 and 11 in the SPOOK notes.
+ * @method updateSpookParams
+ * @param {number} timeStep
+ */
+Equation.prototype.updateSpookParams = function(timeStep){
+ var k = this.stiffness,
+ d = this.relaxation,
+ h = timeStep;
+ this.a = 4.0 / (h * (1 + 4 * d));
+ this.b = (4.0 * d) / (1 + 4 * d);
+ this.eps = 4.0 / (h * h * k * (1 + 4 * d));
+ this.h = timeStep;
+};
+
+function Gmult(G,vi,wi,vj,wj){
+ return G[0] * vi[0] +
+ G[1] * vi[1] +
+ G[2] * wi +
+ G[3] * vj[0] +
+ G[4] * vj[1] +
+ G[5] * wj;
+}
+
+/**
+ * Computes the RHS of the SPOOK equation
+ * @method computeB
+ * @return {Number}
+ */
+Equation.prototype.computeB = function(a,b,h){
+ var GW = this.computeGW();
+ var Gq = this.computeGq();
+ var GiMf = this.computeGiMf();
+ return - Gq * a - GW * b - GiMf*h;
+};
+
+/**
+ * Computes G*q, where q are the generalized body coordinates
+ * @method computeGq
+ * @return {Number}
+ */
+var qi = vec2.create(),
+ qj = vec2.create();
+Equation.prototype.computeGq = function(){
+ var G = this.G,
+ bi = this.bi,
+ bj = this.bj,
+ xi = bi.position,
+ xj = bj.position,
+ ai = bi.angle,
+ aj = bj.angle;
+
+ // Transform to the given body frames
+ /*
+ vec2.rotate(qi,this.xi,ai);
+ vec2.rotate(qj,this.xj,aj);
+ vec2.add(qi,qi,xi);
+ vec2.add(qj,qj,xj);
+ */
+
+ return Gmult(G, qi, ai, qj, aj) + this.offset;
+};
+
+var tmp_i = vec2.create(),
+ tmp_j = vec2.create();
+Equation.prototype.transformedGmult = function(G,vi,wi,vj,wj){
+ // Transform velocity to the given body frames
+ // v_p = v + w x r
+ /*
+ vec2.rotate(tmp_i,this.xi,Math.PI / 2 + this.bi.angle); // Get r, and rotate 90 degrees. We get the "x r" part
+ vec2.rotate(tmp_j,this.xj,Math.PI / 2 + this.bj.angle);
+ vec2.scale(tmp_i,tmp_i,wi); // Temp vectors are now (w x r)
+ vec2.scale(tmp_j,tmp_j,wj);
+ vec2.add(tmp_i,tmp_i,vi);
+ vec2.add(tmp_j,tmp_j,vj);
+ */
+
+ // Note: angular velocity is same
+ return Gmult(G,vi,wi,vj,wj);
+};
+
+/**
+ * Computes G*W, where W are the body velocities
+ * @method computeGW
+ * @return {Number}
+ */
+Equation.prototype.computeGW = function(){
+ var G = this.G,
+ bi = this.bi,
+ bj = this.bj,
+ vi = bi.velocity,
+ vj = bj.velocity,
+ wi = bi.angularVelocity,
+ wj = bj.angularVelocity;
+ return this.transformedGmult(G,vi,wi,vj,wj);
+};
+
+/**
+ * Computes G*Wlambda, where W are the body velocities
+ * @method computeGWlambda
+ * @return {Number}
+ */
+Equation.prototype.computeGWlambda = function(){
+ var G = this.G,
+ bi = this.bi,
+ bj = this.bj,
+ vi = bi.vlambda,
+ vj = bj.vlambda,
+ wi = bi.wlambda,
+ wj = bj.wlambda;
+ return Gmult(G,vi,wi,vj,wj);
+};
+
+/**
+ * Computes G*inv(M)*f, where M is the mass matrix with diagonal blocks for each body, and f are the forces on the bodies.
+ * @method computeGiMf
+ * @return {Number}
+ */
+var iMfi = vec2.create(),
+ iMfj = vec2.create();
+Equation.prototype.computeGiMf = function(){
+ var bi = this.bi,
+ bj = this.bj,
+ fi = bi.force,
+ ti = bi.angularForce,
+ fj = bj.force,
+ tj = bj.angularForce,
+ invMassi = bi.invMass,
+ invMassj = bj.invMass,
+ invIi = bi.invInertia,
+ invIj = bj.invInertia,
+ G = this.G;
+
+ vec2.scale(iMfi, fi,invMassi);
+ vec2.scale(iMfj, fj,invMassj);
+
+ return this.transformedGmult(G,iMfi,ti*invIi,iMfj,tj*invIj);
+};
+
+/**
+ * Computes G*inv(M)*G'
+ * @method computeGiMGt
+ * @return {Number}
+ */
+Equation.prototype.computeGiMGt = function(){
+ var bi = this.bi,
+ bj = this.bj,
+ invMassi = bi.invMass,
+ invMassj = bj.invMass,
+ invIi = bi.invInertia,
+ invIj = bj.invInertia,
+ G = this.G;
+
+ return G[0] * G[0] * invMassi +
+ G[1] * G[1] * invMassi +
+ G[2] * G[2] * invIi +
+ G[3] * G[3] * invMassj +
+ G[4] * G[4] * invMassj +
+ G[5] * G[5] * invIj;
+};
+
+var addToWlambda_temp = vec2.create(),
+ addToWlambda_Gi = vec2.create(),
+ addToWlambda_Gj = vec2.create(),
+ addToWlambda_ri = vec2.create(),
+ addToWlambda_rj = vec2.create(),
+ addToWlambda_Mdiag = vec2.create();
+var tmpMat1 = mat2.create(),
+ tmpMat2 = mat2.create();
+
+/**
+ * Add constraint velocity to the bodies.
+ * @method addToWlambda
+ * @param {Number} deltalambda
+ */
+Equation.prototype.addToWlambda = function(deltalambda){
+ var bi = this.bi,
+ bj = this.bj,
+ temp = addToWlambda_temp,
+ imMat1 = tmpMat1,
+ imMat2 = tmpMat2,
+ Gi = addToWlambda_Gi,
+ Gj = addToWlambda_Gj,
+ ri = addToWlambda_ri,
+ rj = addToWlambda_rj,
+ Mdiag = addToWlambda_Mdiag,
+ G = this.G;
+
+ Gi[0] = G[0];
+ Gi[1] = G[1];
+ Gj[0] = G[3];
+ Gj[1] = G[4];
+
+ /*
+ mat2.identity(imMat1);
+ mat2.identity(imMat2);
+ imMat1[0] = imMat1[3] = bi.invMass;
+ imMat2[0] = imMat2[3] = bj.invMass;
+ */
+
+
+ /*
+ vec2.rotate(ri,this.xi,bi.angle);
+ vec2.rotate(rj,this.xj,bj.angle);
+ */
+
+ // Add to linear velocity
+ // v_lambda += inv(M) * delta_lamba * G
+ //vec2.set(Mdiag,bi.invMass,bi.invMass);
+ //vec2.scale(temp,vec2.transformMat2(temp,Gi,imMat1),deltalambda);
+ vec2.scale(temp,Gi,bi.invMass*deltalambda);
+ vec2.add( bi.vlambda, bi.vlambda, temp);
+ // This impulse is in the offset frame
+ // Also add contribution to angular
+ //bi.wlambda -= vec2.crossLength(temp,ri);
+
+ //vec2.set(Mdiag,bj.invMass,bj.invMass);
+ //vec2.scale(temp,vec2.transformMat2(temp,Gj,imMat2),deltalambda);
+ vec2.scale(temp,Gj,bj.invMass*deltalambda);
+ vec2.add( bj.vlambda, bj.vlambda, temp);
+ //bj.wlambda -= vec2.crossLength(temp,rj);
+
+ // Add to angular velocity
+ bi.wlambda += bi.invInertia * G[2] * deltalambda;
+ bj.wlambda += bj.invInertia * G[5] * deltalambda;
+};
+
+function massMatVecMultiply(out, m, v) {
+ out[0] = v[0] * m;
+ out[1] = v[1] * m;
+ return out;
+}
+
+/**
+ * Compute the denominator part of the SPOOK equation: C = G*inv(M)*G' + eps
+ * @method computeInvC
+ * @param {Number} eps
+ * @return {Number}
+ */
+Equation.prototype.computeInvC = function(eps){
+ return 1.0 / (this.computeGiMGt() + eps);
+};
+
+},{"../math/mat2":31,"../math/vec2":33,"../utils/Utils":49}],25:[function(require,module,exports){
+var mat2 = require('../math/mat2')
+, vec2 = require('../math/vec2')
+, Equation = require('./Equation')
+, Utils = require('../utils/Utils')
+
+module.exports = FrictionEquation;
+
+/**
+ * Constrains the slipping in a contact along a tangent
+ *
+ * @class FrictionEquation
+ * @constructor
+ * @param {Body} bi
+ * @param {Body} bj
+ * @param {Number} slipForce
+ * @extends {Equation}
+ */
+function FrictionEquation(bi,bj,slipForce){
+ Equation.call(this,bi,bj,-slipForce,slipForce);
+
+ /**
+ * Relative vector from center of body i to the contact point, in world coords.
+ * @property ri
+ * @type {Float32Array}
+ */
+ this.ri = vec2.create();
+
+ /**
+ * Relative vector from center of body j to the contact point, in world coords.
+ * @property rj
+ * @type {Float32Array}
+ */
+ this.rj = vec2.create();
+
+ /**
+ * Tangent vector that the friction force will act along, in world coords.
+ * @property t
+ * @type {Float32Array}
+ */
+ this.t = vec2.create();
+
+ /**
+ * A ContactEquation connected to this friction. The contact equation can be used to rescale the max force for the friction.
+ * @property contactEquation
+ * @type {ContactEquation}
+ */
+ this.contactEquation = null;
+
+ /**
+ * The shape in body i that triggered this friction.
+ * @property shapeA
+ * @type {Shape}
+ * @todo Needed? The shape can be looked up via contactEquation.shapeA...
+ */
+ this.shapeA = null;
+
+ /**
+ * The shape in body j that triggered this friction.
+ * @property shapeB
+ * @type {Shape}
+ * @todo Needed? The shape can be looked up via contactEquation.shapeB...
+ */
+ this.shapeB = null;
+
+ /**
+ * The friction coefficient to use.
+ * @property frictionCoefficient
+ * @type {Number}
+ */
+ this.frictionCoefficient = 0.3;
+};
+FrictionEquation.prototype = new Equation();
+FrictionEquation.prototype.constructor = FrictionEquation;
+
+/**
+ * Set the slipping condition for the constraint. The friction force cannot be
+ * larger than this value.
+ * @method setSlipForce
+ * @param {Number} slipForce
+ * @deprecated Use .frictionCoefficient instead
+ */
+FrictionEquation.prototype.setSlipForce = function(slipForce){
+ this.maxForce = slipForce;
+ this.minForce = -slipForce;
+};
+
+FrictionEquation.prototype.computeB = function(a,b,h){
+ var bi = this.bi,
+ bj = this.bj,
+ ri = this.ri,
+ rj = this.rj,
+ t = this.t,
+ G = this.G;
+
+ // G = [-t -rixt t rjxt]
+ // And remember, this is a pure velocity constraint, g is always zero!
+ G[0] = -t[0];
+ G[1] = -t[1];
+ G[2] = -vec2.crossLength(ri,t);
+ G[3] = t[0];
+ G[4] = t[1];
+ G[5] = vec2.crossLength(rj,t);
+
+ var GW = this.computeGW();
+ var GiMf = this.computeGiMf();
+
+ var B = /* - g * a */ - GW * b - h*GiMf;
+
+ return B;
+};
+
+},{"../math/mat2":31,"../math/vec2":33,"../utils/Utils":49,"./Equation":24}],26:[function(require,module,exports){
+var Equation = require("./Equation"),
+ vec2 = require('../math/vec2');
+
+module.exports = RotationalLockEquation;
+
+/**
+ * Locks the relative angle between two bodies. The constraint tries to keep the dot product between two vectors, local in each body, to zero. The local angle in body i is a parameter.
+ *
+ * @class RotationalLockEquation
+ * @constructor
+ * @extends Equation
+ * @param {Body} bi
+ * @param {Body} bj
+ * @param {Object} options
+ * @param {Number} options.angle Angle to add to the local vector in body i.
+ */
+function RotationalLockEquation(bi,bj,options){
+ options = options || {};
+ Equation.call(this,bi,bj,-Number.MAX_VALUE,Number.MAX_VALUE);
+ this.angle = options.angle || 0;
+
+ var G = this.G;
+ G[2] = 1;
+ G[5] = -1;
+};
+RotationalLockEquation.prototype = new Equation();
+RotationalLockEquation.prototype.constructor = RotationalLockEquation;
+
+var worldVectorA = vec2.create(),
+ worldVectorB = vec2.create(),
+ xAxis = vec2.fromValues(1,0),
+ yAxis = vec2.fromValues(0,1);
+RotationalLockEquation.prototype.computeGq = function(){
+ vec2.rotate(worldVectorA,xAxis,this.bi.angle+this.angle);
+ vec2.rotate(worldVectorB,yAxis,this.bj.angle);
+ return vec2.dot(worldVectorA,worldVectorB);
+};
+
+},{"../math/vec2":33,"./Equation":24}],27:[function(require,module,exports){
+var Equation = require("./Equation"),
+ vec2 = require('../math/vec2');
+
+module.exports = RotationalVelocityEquation;
+
+/**
+ * Syncs rotational velocity of two bodies, or sets a relative velocity (motor).
+ *
+ * @class RotationalVelocityEquation
+ * @constructor
+ * @extends Equation
+ * @param {Body} bi
+ * @param {Body} bj
+ */
+function RotationalVelocityEquation(bi,bj){
+ Equation.call(this,bi,bj,-Number.MAX_VALUE,Number.MAX_VALUE);
+ this.relativeVelocity = 1;
+ this.ratio = 1;
+};
+RotationalVelocityEquation.prototype = new Equation();
+RotationalVelocityEquation.prototype.constructor = RotationalVelocityEquation;
+RotationalVelocityEquation.prototype.computeB = function(a,b,h){
+ var G = this.G;
+ G[2] = -1;
+ G[5] = this.ratio;
+
+ var GiMf = this.computeGiMf();
+ var GW = this.computeGW() + this.relativeVelocity;
+ var B = - GW * b - h*GiMf;
+
+ return B;
+};
+
+},{"../math/vec2":33,"./Equation":24}],28:[function(require,module,exports){
+/**
+ * Base class for objects that dispatches events.
+ * @class EventEmitter
+ * @constructor
+ */
+var EventEmitter = function () {}
+
+module.exports = EventEmitter;
+
+EventEmitter.prototype = {
+ constructor: EventEmitter,
+
+ /**
+ * Add an event listener
+ * @method on
+ * @param {String} type
+ * @param {Function} listener
+ * @return {EventEmitter} The self object, for chainability.
+ */
+ on: function ( type, listener ) {
+ if ( this._listeners === undefined ) this._listeners = {};
+ var listeners = this._listeners;
+ if ( listeners[ type ] === undefined ) {
+ listeners[ type ] = [];
+ }
+ if ( listeners[ type ].indexOf( listener ) === - 1 ) {
+ listeners[ type ].push( listener );
+ }
+ return this;
+ },
+
+ /**
+ * Check if an event listener is added
+ * @method has
+ * @param {String} type
+ * @param {Function} listener
+ * @return {Boolean}
+ */
+ has: function ( type, listener ) {
+ if ( this._listeners === undefined ) return false;
+ var listeners = this._listeners;
+ if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) {
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Remove an event listener
+ * @method off
+ * @param {String} type
+ * @param {Function} listener
+ * @return {EventEmitter} The self object, for chainability.
+ */
+ off: function ( type, listener ) {
+ if ( this._listeners === undefined ) return this;
+ var listeners = this._listeners;
+ var index = listeners[ type ].indexOf( listener );
+ if ( index !== - 1 ) {
+ listeners[ type ].splice( index, 1 );
+ }
+ return this;
+ },
+
+ /**
+ * Emit an event.
+ * @method emit
+ * @param {Object} event
+ * @param {String} event.type
+ * @return {EventEmitter} The self object, for chainability.
+ */
+ emit: function ( event ) {
+ if ( this._listeners === undefined ) return this;
+ var listeners = this._listeners;
+ var listenerArray = listeners[ event.type ];
+ if ( listenerArray !== undefined ) {
+ event.target = this;
+ for ( var i = 0, l = listenerArray.length; i < l; i ++ ) {
+ listenerArray[ i ].call( this, event );
+ }
+ }
+ return this;
+ }
+};
+
+},{}],29:[function(require,module,exports){
+module.exports = ContactMaterial;
+
+var idCounter = 0;
+
+/**
+ * Defines a physics material.
+ * @class ContactMaterial
+ * @constructor
+ * @param {Material} materialA
+ * @param {Material} materialB
+ * @param {Object} [options]
+ * @param {Number} options.friction
+ * @param {Number} options.restitution
+ * @author schteppe
+ */
+function ContactMaterial(materialA, materialB, options){
+ options = options || {};
+
+ /**
+ * The contact material identifier
+ * @property id
+ * @type {Number}
+ */
+ this.id = idCounter++;
+
+ /**
+ * First material participating in the contact material
+ * @property materialA
+ * @type {Material}
+ */
+ this.materialA = materialA;
+
+ /**
+ * Second material participating in the contact material
+ * @property materialB
+ * @type {Material}
+ */
+ this.materialB = materialB;
+
+ /**
+ * Friction to use in the contact of these two materials
+ * @property friction
+ * @type {Number}
+ */
+ this.friction = typeof(options.friction) !== "undefined" ? Number(options.friction) : 0.3;
+
+ /**
+ * Restitution to use in the contact of these two materials
+ * @property restitution
+ * @type {Number}
+ */
+ this.restitution = typeof(options.restitution) !== "undefined" ? Number(options.restitution) : 0.0;
+
+ /**
+ * Stiffness of the resulting ContactEquation that this ContactMaterial generate
+ * @property stiffness
+ * @type {Number}
+ */
+ this.stiffness = typeof(options.stiffness) !== "undefined" ? Number(options.stiffness) : 1e7;
+
+ /**
+ * Relaxation of the resulting ContactEquation that this ContactMaterial generate
+ * @property relaxation
+ * @type {Number}
+ */
+ this.relaxation = typeof(options.relaxation) !== "undefined" ? Number(options.relaxation) : 3;
+
+ /**
+ * Stiffness of the resulting FrictionEquation that this ContactMaterial generate
+ * @property frictionStiffness
+ * @type {Number}
+ */
+ this.frictionStiffness = typeof(options.frictionStiffness) !== "undefined" ? Number(options.frictionStiffness) : 1e7;
+
+ /**
+ * Relaxation of the resulting FrictionEquation that this ContactMaterial generate
+ * @property frictionRelaxation
+ * @type {Number}
+ */
+ this.frictionRelaxation = typeof(options.frictionRelaxation) !== "undefined" ? Number(options.frictionRelaxation) : 3;
+};
+
+},{}],30:[function(require,module,exports){
+module.exports = Material;
+
+var idCounter = 0;
+
+/**
+ * Defines a physics material.
+ * @class Material
+ * @constructor
+ * @param string name
+ * @author schteppe
+ */
+function Material(){
+ /**
+ * The material identifier
+ * @property id
+ * @type {Number}
+ */
+ this.id = idCounter++;
+};
+
+},{}],31:[function(require,module,exports){
+/**
+ * The mat2 object from glMatrix, extended with the functions documented here. See http://glmatrix.net for full doc.
+ * @class mat2
+ */
+
+// Only import mat2 from gl-matrix and skip the rest
+var mat2 = require('../../node_modules/gl-matrix/src/gl-matrix/mat2').mat2;
+
+// Export everything
+module.exports = mat2;
+
+},{"../../node_modules/gl-matrix/src/gl-matrix/mat2":1}],32:[function(require,module,exports){
+
+ /*
+ PolyK library
+ url: http://polyk.ivank.net
+ Released under MIT licence.
+
+ Copyright (c) 2012 Ivan Kuckir
+
+ 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.
+ */
+
+ var PolyK = {};
+
+ /*
+ Is Polygon self-intersecting?
+
+ O(n^2)
+ */
+ /*
+ PolyK.IsSimple = function(p)
+ {
+ var n = p.length>>1;
+ if(n<4) return true;
+ var a1 = new PolyK._P(), a2 = new PolyK._P();
+ var b1 = new PolyK._P(), b2 = new PolyK._P();
+ var c = new PolyK._P();
+
+ for(var i=0; i>1;
+ if(n<3) return [];
+ var tgs = [];
+ var avl = [];
+ for(var i=0; i 3)
+ {
+ var i0 = avl[(i+0)%al];
+ var i1 = avl[(i+1)%al];
+ var i2 = avl[(i+2)%al];
+
+ var ax = p[2*i0], ay = p[2*i0+1];
+ var bx = p[2*i1], by = p[2*i1+1];
+ var cx = p[2*i2], cy = p[2*i2+1];
+
+ var earFound = false;
+ if(PolyK._convex(ax, ay, bx, by, cx, cy))
+ {
+ earFound = true;
+ for(var j=0; j 3*al) break; // no convex angles :(
+ }
+ tgs.push(avl[0], avl[1], avl[2]);
+ return tgs;
+ }
+ /*
+ PolyK.ContainsPoint = function(p, px, py)
+ {
+ var n = p.length>>1;
+ var ax, ay, bx = p[2*n-2]-px, by = p[2*n-1]-py;
+ var depth = 0;
+ for(var i=0; i=0 && by>=0) continue; // both "up" or both "donw"
+ if(ax< 0 && bx< 0) continue;
+
+ var lx = ax + (bx-ax)*(-ay)/(by-ay);
+ if(lx>0) depth++;
+ }
+ return (depth & 1) == 1;
+ }
+
+ PolyK.Slice = function(p, ax, ay, bx, by)
+ {
+ if(PolyK.ContainsPoint(p, ax, ay) || PolyK.ContainsPoint(p, bx, by)) return [p.slice(0)];
+
+ var a = new PolyK._P(ax, ay);
+ var b = new PolyK._P(bx, by);
+ var iscs = []; // intersections
+ var ps = []; // points
+ for(var i=0; i 0)
+ {
+ var n = ps.length;
+ var i0 = iscs[0];
+ var i1 = iscs[1];
+ var ind0 = ps.indexOf(i0);
+ var ind1 = ps.indexOf(i1);
+ var solved = false;
+
+ if(PolyK._firstWithFlag(ps, ind0) == ind1) solved = true;
+ else
+ {
+ i0 = iscs[1];
+ i1 = iscs[0];
+ ind0 = ps.indexOf(i0);
+ ind1 = ps.indexOf(i1);
+ if(PolyK._firstWithFlag(ps, ind0) == ind1) solved = true;
+ }
+ if(solved)
+ {
+ dir--;
+ var pgn = PolyK._getPoints(ps, ind0, ind1);
+ pgs.push(pgn);
+ ps = PolyK._getPoints(ps, ind1, ind0);
+ i0.flag = i1.flag = false;
+ iscs.splice(0,2);
+ if(iscs.length == 0) pgs.push(ps);
+ }
+ else { dir++; iscs.reverse(); }
+ if(dir>1) break;
+ }
+ var result = [];
+ for(var i=0; i>1, isc);
+ }
+ b1.x = b2.x; b1.y = b2.y;
+ b2.x = p[0]; b2.y = p[1];
+ PolyK._pointLineDist(a1, b1, b2, l>>1, isc);
+
+ var idst = 1/isc.dist;
+ isc.norm.x = (x-isc.point.x)*idst;
+ isc.norm.y = (y-isc.point.y)*idst;
+ return isc;
+ }
+
+ PolyK._pointLineDist = function(p, a, b, edge, isc)
+ {
+ var x = p.x, y = p.y, x1 = a.x, y1 = a.y, x2 = b.x, y2 = b.y;
+
+ var A = x - x1;
+ var B = y - y1;
+ var C = x2 - x1;
+ var D = y2 - y1;
+
+ var dot = A * C + B * D;
+ var len_sq = C * C + D * D;
+ var param = dot / len_sq;
+
+ var xx, yy;
+
+ if (param < 0 || (x1 == x2 && y1 == y2)) {
+ xx = x1;
+ yy = y1;
+ }
+ else if (param > 1) {
+ xx = x2;
+ yy = y2;
+ }
+ else {
+ xx = x1 + param * C;
+ yy = y1 + param * D;
+ }
+
+ var dx = x - xx;
+ var dy = y - yy;
+ var dst = Math.sqrt(dx * dx + dy * dy);
+ if(dst= 0) && (v >= 0) && (u + v < 1);
+ }
+ /*
+ PolyK._RayLineIntersection = function(a1, a2, b1, b2, c)
+ {
+ var dax = (a1.x-a2.x), dbx = (b1.x-b2.x);
+ var day = (a1.y-a2.y), dby = (b1.y-b2.y);
+
+ var Den = dax*dby - day*dbx;
+ if (Den == 0) return null; // parallel
+
+ var A = (a1.x * a2.y - a1.y * a2.x);
+ var B = (b1.x * b2.y - b1.y * b2.x);
+
+ var I = c;
+ var iDen = 1/Den;
+ I.x = ( A*dbx - dax*B ) * iDen;
+ I.y = ( A*dby - day*B ) * iDen;
+
+ if(!PolyK._InRect(I, b1, b2)) return null;
+ if((day>0 && I.y>a1.y) || (day<0 && I.y0 && I.x>a1.x) || (dax<0 && I.x=Math.min(b.y, c.y) && a.y<=Math.max(b.y, c.y));
+ if (b.y == c.y) return (a.x>=Math.min(b.x, c.x) && a.x<=Math.max(b.x, c.x));
+
+ if(a.x >= Math.min(b.x, c.x) && a.x <= Math.max(b.x, c.x)
+ && a.y >= Math.min(b.y, c.y) && a.y <= Math.max(b.y, c.y))
+ return true;
+ return false;
+ }
+ */
+ PolyK._convex = function(ax, ay, bx, by, cx, cy)
+ {
+ return (ay-by)*(cx-bx) + (bx-ax)*(cy-by) >= 0;
+ }
+ /*
+ PolyK._P = function(x,y)
+ {
+ this.x = x;
+ this.y = y;
+ this.flag = false;
+ }
+ PolyK._P.prototype.toString = function()
+ {
+ return "Point ["+this.x+", "+this.y+"]";
+ }
+ PolyK._P.dist = function(a,b)
+ {
+ var dx = b.x-a.x;
+ var dy = b.y-a.y;
+ return Math.sqrt(dx*dx + dy*dy);
+ }
+
+ PolyK._tp = [];
+ for(var i=0; i<10; i++) PolyK._tp.push(new PolyK._P(0,0));
+ */
+
+module.exports = PolyK;
+
+},{}],33:[function(require,module,exports){
+/**
+ * The vec2 object from glMatrix, extended with the functions documented here. See http://glmatrix.net for full doc.
+ * @class vec2
+ */
+
+// Only import vec2 from gl-matrix and skip the rest
+var vec2 = require('../../node_modules/gl-matrix/src/gl-matrix/vec2').vec2;
+
+// Now add some extensions
+
+/**
+ * Get the vector x component
+ * @method getX
+ * @static
+ * @param {Float32Array} a
+ * @return {Number}
+ */
+vec2.getX = function(a){
+ return a[0];
+};
+
+/**
+ * Get the vector y component
+ * @method getY
+ * @static
+ * @param {Float32Array} a
+ * @return {Number}
+ */
+vec2.getY = function(a){
+ return a[1];
+};
+
+/**
+ * Make a cross product and only return the z component
+ * @method crossLength
+ * @static
+ * @param {Float32Array} a
+ * @param {Float32Array} b
+ * @return {Number}
+ */
+vec2.crossLength = function(a,b){
+ return a[0] * b[1] - a[1] * b[0];
+};
+
+/**
+ * Cross product between a vector and the Z component of a vector
+ * @method crossVZ
+ * @static
+ * @param {Float32Array} out
+ * @param {Float32Array} vec
+ * @param {Number} zcomp
+ * @return {Number}
+ */
+vec2.crossVZ = function(out, vec, zcomp){
+ vec2.rotate(out,vec,-Math.PI/2);// Rotate according to the right hand rule
+ vec2.scale(out,out,zcomp); // Scale with z
+ return out;
+};
+
+/**
+ * Cross product between a vector and the Z component of a vector
+ * @method crossZV
+ * @static
+ * @param {Float32Array} out
+ * @param {Number} zcomp
+ * @param {Float32Array} vec
+ * @return {Number}
+ */
+vec2.crossZV = function(out, zcomp, vec){
+ vec2.rotate(out,vec,Math.PI/2); // Rotate according to the right hand rule
+ vec2.scale(out,out,zcomp); // Scale with z
+ return out;
+};
+
+/**
+ * Rotate a vector by an angle
+ * @method rotate
+ * @static
+ * @param {Float32Array} out
+ * @param {Float32Array} a
+ * @param {Number} angle
+ */
+vec2.rotate = function(out,a,angle){
+ var c = Math.cos(angle),
+ s = Math.sin(angle),
+ x = a[0],
+ y = a[1];
+ out[0] = c*x -s*y;
+ out[1] = s*x +c*y;
+};
+
+vec2.toLocalFrame = function(out, worldPoint, framePosition, frameAngle){
+ vec2.copy(out, worldPoint);
+ vec2.sub(out, out, framePosition);
+ vec2.rotate(out, out, -frameAngle);
+};
+
+vec2.toGlobalFrame = function(out, localPoint, framePosition, frameAngle){
+ vec2.copy(out, localPoint);
+ vec2.rotate(out, out, frameAngle);
+ vec2.add(out, out, framePosition);
+};
+
+/**
+ * Compute centroid of a triangle spanned by vectors a,b,c. See http://easycalculation.com/analytical/learn-centroid.php
+ * @method centroid
+ * @static
+ * @param {Float32Array} out
+ * @param {Float32Array} a
+ * @param {Float32Array} b
+ * @param {Float32Array} c
+ * @return {Float32Array} The out object
+ */
+vec2.centroid = function(out, a, b, c){
+ vec2.add(out, a, b);
+ vec2.add(out, out, c);
+ vec2.scale(out, out, 1/3);
+ return out;
+};
+
+// Export everything
+module.exports = vec2;
+
+},{"../../node_modules/gl-matrix/src/gl-matrix/vec2":2}],34:[function(require,module,exports){
+var vec2 = require('../math/vec2')
+, decomp = require('poly-decomp')
+, Convex = require('../shapes/Convex')
+, AABB = require('../collision/AABB')
+, EventEmitter = require('../events/EventEmitter')
+
+module.exports = Body;
+
+/**
+ * A rigid body. Has got a center of mass, position, velocity and a number of
+ * shapes that are used for collisions.
+ *
+ * @class Body
+ * @constructor
+ * @param {Object} [options]
+ * @param {Number} [options.mass=0] A number >= 0. If zero, the .motionState will be set to Body.STATIC.
+ * @param {Float32Array|Array} [options.position]
+ * @param {Float32Array|Array} [options.velocity]
+ * @param {Number} [options.angle=0]
+ * @param {Number} [options.angularVelocity=0]
+ * @param {Float32Array|Array} [options.force]
+ * @param {Number} [options.angularForce=0]
+ * @param {Number} [options.fixedRotation=false]
+ *
+ * @todo Should not take mass as argument to Body, but as density to each Shape
+ */
+function Body(options){
+ options = options || {};
+
+ EventEmitter.call(this);
+
+ /**
+ * The body identifyer
+ * @property id
+ * @type {Number}
+ */
+ this.id = ++Body._idCounter;
+
+ /**
+ * The world that this body is added to. This property is set to NULL if the body is not added to any world.
+ * @property world
+ * @type {World}
+ */
+ this.world = null;
+
+ /**
+ * The shapes of the body. The local transform of the shape in .shapes[i] is
+ * defined by .shapeOffsets[i] and .shapeAngles[i].
+ *
+ * @property shapes
+ * @type {Array}
+ */
+ this.shapes = [];
+
+ /**
+ * The local shape offsets, relative to the body center of mass. This is an
+ * array of Float32Array.
+ * @property shapeOffsets
+ * @type {Array}
+ */
+ this.shapeOffsets = [];
+
+ /**
+ * The body-local shape angle transforms. This is an array of numbers (angles).
+ * @property shapeAngles
+ * @type {Array}
+ */
+ this.shapeAngles = [];
+
+ /**
+ * The mass of the body.
+ * @property mass
+ * @type {number}
+ */
+ this.mass = options.mass || 0;
+
+ /**
+ * The inverse mass of the body.
+ * @property invMass
+ * @type {number}
+ */
+ this.invMass = 0;
+
+ /**
+ * The inertia of the body around the Z axis.
+ * @property inertia
+ * @type {number}
+ */
+ this.inertia = 0;
+
+ /**
+ * The inverse inertia of the body.
+ * @property invInertia
+ * @type {number}
+ */
+ this.invInertia = 0;
+
+ /**
+ * Set to true if you want to fix the rotation of the body.
+ * @property fixedRotation
+ * @type {Boolean}
+ */
+ this.fixedRotation = !!options.fixedRotation || false;
+
+ this.updateMassProperties();
+
+ /**
+ * The position of the body
+ * @property position
+ * @type {Array}
+ */
+ this.position = vec2.fromValues(0,0);
+ if(options.position) vec2.copy(this.position, options.position);
+
+ /**
+ * The interpolated position of the body.
+ * @property interpolatedPosition
+ * @type {Array}
+ */
+ this.interpolatedPosition = vec2.fromValues(0,0);
+
+ /**
+ * The velocity of the body
+ * @property velocity
+ * @type {Float32Array}
+ */
+ this.velocity = vec2.fromValues(0,0);
+ if(options.velocity) vec2.copy(this.velocity, options.velocity);
+
+ /**
+ * Constraint velocity that was added to the body during the last step.
+ * @property vlambda
+ * @type {Float32Array}
+ */
+ this.vlambda = vec2.fromValues(0,0);
+
+ /**
+ * Angular constraint velocity that was added to the body during last step.
+ * @property wlambda
+ * @type {Float32Array}
+ */
+ this.wlambda = 0;
+
+ /**
+ * The angle of the body
+ * @property angle
+ * @type {number}
+ */
+ this.angle = options.angle || 0;
+
+ /**
+ * The angular velocity of the body
+ * @property angularVelocity
+ * @type {number}
+ */
+ this.angularVelocity = options.angularVelocity || 0;
+
+ /**
+ * The force acting on the body
+ * @property force
+ * @type {Float32Array}
+ */
+ this.force = vec2.create();
+ if(options.force) vec2.copy(this.force, options.force);
+
+ /**
+ * The angular force acting on the body
+ * @property angularForce
+ * @type {number}
+ */
+ this.angularForce = options.angularForce || 0;
+
+ /**
+ * The linear damping acting on the body in the velocity direction
+ * @property damping
+ * @type {Number}
+ */
+ this.damping = typeof(options.damping)=="number" ? options.damping : 0.1;
+
+ /**
+ * The angular force acting on the body
+ * @property angularDamping
+ * @type {Number}
+ */
+ this.angularDamping = typeof(options.angularDamping)=="number" ? options.angularDamping : 0.1;
+
+ /**
+ * The type of motion this body has. Should be one of: Body.STATIC (the body
+ * does not move), Body.DYNAMIC (body can move and respond to collisions)
+ * and Body.KINEMATIC (only moves according to its .velocity).
+ *
+ * @property motionState
+ * @type {number}
+ *
+ * @example
+ * // This body will move and interact with other bodies
+ * var dynamicBody = new Body();
+ * dynamicBody.motionState = Body.DYNAMIC;
+ *
+ * @example
+ * // This body will not move at all
+ * var staticBody = new Body();
+ * staticBody.motionState = Body.STATIC;
+ *
+ * @example
+ * // This body will only move if you change its velocity
+ * var kinematicBody = new Body();
+ * kinematicBody.motionState = Body.KINEMATIC;
+ */
+ this.motionState = this.mass == 0 ? Body.STATIC : Body.DYNAMIC;
+
+ /**
+ * Bounding circle radius
+ * @property boundingRadius
+ * @type {Number}
+ */
+ this.boundingRadius = 0;
+
+ /**
+ * Bounding box of this body
+ * @property aabb
+ * @type {AABB}
+ */
+ this.aabb = new AABB();
+
+ /**
+ * Indicates if the AABB needs update. Update it with .updateAABB()
+ * @property aabbNeedsUpdate
+ * @type {Boolean}
+ */
+ this.aabbNeedsUpdate = true;
+
+ /**
+ * If true, the body will automatically fall to sleep.
+ * @property allowSleep
+ * @type {Boolean}
+ */
+ this.allowSleep = false;
+
+ /**
+ * One of Body.AWAKE, Body.SLEEPY, Body.SLEEPING
+ * @property sleepState
+ * @type {Number}
+ */
+ this.sleepState = Body.AWAKE;
+
+ /**
+ * If the speed (the norm of the velocity) is smaller than this value, the body is considered sleepy.
+ * @property sleepSpeedLimit
+ * @type {Number}
+ */
+ this.sleepSpeedLimit = 0.1;
+
+ /**
+ * If the body has been sleepy for this sleepTimeLimit seconds, it is considered sleeping.
+ * @property sleepTimeLimit
+ * @type {Number}
+ */
+ this.sleepTimeLimit = 1;
+
+ this.timeLastSleepy = 0;
+
+ this.concavePath = null;
+
+ this.lastDampingScale = 1;
+ this.lastAngularDampingScale = 1;
+ this.lastDampingTimeStep = -1;
+};
+
+Body.prototype = new EventEmitter();
+
+Body._idCounter = 0;
+
+/**
+ * Set the total density of the body
+ * @method setDensity
+ */
+Body.prototype.setDensity = function(density) {
+ var totalArea = this.getArea();
+ this.mass = totalArea * density;
+ this.updateMassProperties();
+};
+
+/**
+ * Get the total area of all shapes in the body
+ * @method setDensity
+ */
+Body.prototype.getArea = function() {
+ var totalArea = 0;
+ for(var i=0; i radius)
+ radius = offset + r;
+ }
+
+ this.boundingRadius = radius;
+};
+
+/**
+ * Add a shape to the body. You can pass a local transform when adding a shape,
+ * so that the shape gets an offset and angle relative to the body center of mass.
+ * Will automatically update the mass properties and bounding radius.
+ *
+ * @method addShape
+ * @param {Shape} shape
+ * @param {Float32Array|Array} [offset] Local body offset of the shape.
+ * @param {Number} [angle] Local body angle.
+ *
+ * @example
+ * var body = new Body(),
+ * shape = new Circle();
+ *
+ * // Add the shape to the body, positioned in the center
+ * body.addShape(shape);
+ *
+ * // Add another shape to the body, positioned 1 unit length from the body center of mass along the local x-axis.
+ * body.addShape(shape,[1,0]);
+ *
+ * // Add another shape to the body, positioned 1 unit length from the body center of mass along the local y-axis, and rotated 90 degrees CCW.
+ * body.addShape(shape,[0,1],Math.PI/2);
+ */
+Body.prototype.addShape = function(shape,offset,angle){
+ angle = angle || 0.0;
+
+ // Copy the offset vector
+ if(offset){
+ offset = vec2.fromValues(offset[0],offset[1]);
+ } else {
+ offset = vec2.fromValues(0,0);
+ }
+
+ this.shapes .push(shape);
+ this.shapeOffsets.push(offset);
+ this.shapeAngles .push(angle);
+ this.updateMassProperties();
+ this.updateBoundingRadius();
+
+ this.aabbNeedsUpdate = true;
+};
+
+/**
+ * Remove a shape
+ * @method removeShape
+ * @param {Shape} shape
+ * @return {Boolean} True if the shape was found and removed, else false.
+ */
+Body.prototype.removeShape = function(shape){
+ var idx = this.shapes.indexOf(shape);
+
+ if(idx != -1){
+ this.shapes.splice(idx,1);
+ this.shapeOffsets.splice(idx,1);
+ this.shapeAngles.splice(idx,1);
+ this.aabbNeedsUpdate = true;
+ return true;
+ } else
+ return false;
+
+};
+
+/**
+ * Updates .inertia, .invMass, .invInertia for this Body. Should be called when
+ * changing the structure or mass of the Body.
+ *
+ * @method updateMassProperties
+ *
+ * @example
+ * body.mass += 1;
+ * body.updateMassProperties();
+ */
+Body.prototype.updateMassProperties = function(){
+ var shapes = this.shapes,
+ N = shapes.length,
+ m = this.mass / N,
+ I = 0;
+
+ if(!this.fixedRotation){
+ for(var i=0; i 0 ? 1/this.mass : 0;
+ this.invInertia = I>0 ? 1/I : 0;
+};
+
+var Body_applyForce_r = vec2.create();
+
+/**
+ * Apply force to a world point. This could for example be a point on the RigidBody surface. Applying force this way will add to Body.force and Body.angularForce.
+ * @method applyForce
+ * @param {Float32Array} force The force to add.
+ * @param {Float32Array} worldPoint A world point to apply the force on.
+ */
+Body.prototype.applyForce = function(force,worldPoint){
+ // Compute point position relative to the body center
+ var r = Body_applyForce_r;
+ vec2.sub(r,worldPoint,this.position);
+
+ // Add linear force
+ vec2.add(this.force,this.force,force);
+
+ // Compute produced rotational force
+ var rotForce = vec2.crossLength(r,force);
+
+ // Add rotational force
+ this.angularForce += rotForce;
+};
+
+/**
+ * Transform a world point to local body frame.
+ * @method toLocalFrame
+ * @param {Float32Array|Array} out The vector to store the result in
+ * @param {Float32Array|Array} worldPoint The input world vector
+ */
+Body.prototype.toLocalFrame = function(out, worldPoint){
+ vec2.toLocalFrame(out, worldPoint, this.position, this.angle);
+};
+
+/**
+ * Transform a local point to world frame.
+ * @method toWorldFrame
+ * @param {Array} out The vector to store the result in
+ * @param {Array} localPoint The input local vector
+ */
+Body.prototype.toWorldFrame = function(out, localPoint){
+ vec2.toGlobalFrame(out, localPoint, this.position, this.angle);
+};
+
+/**
+ * Reads a polygon shape path, and assembles convex shapes from that and puts them at proper offset points.
+ * @method fromPolygon
+ * @param {Array} path An array of 2d vectors, e.g. [[0,0],[0,1],...] that resembles a concave or convex polygon. The shape must be simple and without holes.
+ * @param {Object} [options]
+ * @param {Boolean} [options.optimalDecomp=false] Set to true if you need optimal decomposition. Warning: very slow for polygons with more than 10 vertices.
+ * @param {Boolean} [options.skipSimpleCheck=false] Set to true if you already know that the path is not intersecting itself.
+ * @param {Boolean|Number} [options.removeCollinearPoints=false] Set to a number (angle threshold value) to remove collinear points, or false to keep all points.
+ * @return {Boolean} True on success, else false.
+ */
+Body.prototype.fromPolygon = function(path,options){
+ options = options || {};
+
+ // Remove all shapes
+ for(var i=this.shapes.length; i>=0; --i)
+ this.removeShape(this.shapes[i]);
+
+ var p = new decomp.Polygon();
+ p.vertices = path;
+
+ // Make it counter-clockwise
+ p.makeCCW();
+
+ if(typeof(options.removeCollinearPoints)=="number"){
+ p.removeCollinearPoints(options.removeCollinearPoints);
+ }
+
+ // Check if any line segment intersects the path itself
+ if(typeof(options.skipSimpleCheck) == "undefined"){
+ if(!p.isSimple()) return false;
+ }
+
+ // Save this path for later
+ this.concavePath = p.vertices.slice(0);
+ for(var i=0; ithis for details.
+ * @method applyDamping
+ * @param {number} dt Current time step
+ */
+Body.prototype.applyDamping = function(dt){
+ if(this.motionState & Body.DYNAMIC){ // Only for dynamic bodies
+
+ // Since Math.pow generates garbage we check if we can reuse the scaling coefficient from last step
+ if(dt != this.lastDampingTimeStep){
+ this.lastDampingScale = Math.pow(1.0 - this.damping,dt);
+ this.lastAngularDampingScale = Math.pow(1.0 - this.angularDamping,dt);
+ this.lastDampingTimeStep = dt;
+ }
+
+ var v = this.velocity;
+ vec2.scale(v,v,this.lastDampingScale);
+ this.angularVelocity *= this.lastAngularDampingScale;
+ }
+};
+
+/**
+ * @method wakeUp
+ * @brief Wake the body up.
+ */
+Body.prototype.wakeUp = function(){
+ var s = this.sleepState;
+ this.sleepState = Body.AWAKE;
+ if(s !== Body.AWAKE){
+ this.emit(Body.wakeUpEvent);
+ }
+};
+
+/**
+ * @method sleep
+ * @brief Force body sleep
+ */
+Body.prototype.sleep = function(){
+ this.sleepState = Body.SLEEPING;
+ this.emit(Body.sleepEvent);
+};
+
+/**
+ * @method sleepTick
+ * @param float time The world time in seconds
+ * @brief Called every timestep to update internal sleep timer and change sleep state if needed.
+ */
+Body.prototype.sleepTick = function(time){
+ if(!this.allowSleep)
+ return;
+
+ var sleepState = this.sleepState,
+ speedSquared = vec2.squaredLength(this.velocity) + Math.pow(this.angularVelocity,2),
+ speedLimitSquared = Math.pow(this.sleepSpeedLimit,2);
+ if(sleepState===Body.AWAKE && speedSquared < speedLimitSquared){
+ this.sleepState = Body.SLEEPY; // Sleepy
+ this.timeLastSleepy = time;
+ this.emit(Body.sleepyEvent);
+ } else if(sleepState===Body.SLEEPY && speedSquared > speedLimitSquared){
+ this.wakeUp(); // Wake up
+ } else if(sleepState===Body.SLEEPY && (time - this.timeLastSleepy ) > this.sleepTimeLimit){
+ this.sleep();
+ }
+};
+
+Body.sleepyEvent = {
+ type: "sleepy"
+};
+
+Body.sleepEvent = {
+ type: "sleep"
+};
+
+Body.wakeUpEvent = {
+ type: "wakeup"
+};
+
+/**
+ * Dynamic body.
+ * @property DYNAMIC
+ * @type {Number}
+ * @static
+ */
+Body.DYNAMIC = 1;
+
+/**
+ * Static body.
+ * @property STATIC
+ * @type {Number}
+ * @static
+ */
+Body.STATIC = 2;
+
+/**
+ * Kinematic body.
+ * @property KINEMATIC
+ * @type {Number}
+ * @static
+ */
+Body.KINEMATIC = 4;
+
+/**
+ * @property AWAKE
+ * @type {Number}
+ * @static
+ */
+Body.AWAKE = 0;
+
+/**
+ * @property SLEEPY
+ * @type {Number}
+ * @static
+ */
+Body.SLEEPY = 1;
+
+/**
+ * @property SLEEPING
+ * @type {Number}
+ * @static
+ */
+Body.SLEEPING = 2;
+
+
+},{"../collision/AABB":9,"../events/EventEmitter":28,"../math/vec2":33,"../shapes/Convex":39,"poly-decomp":7}],35:[function(require,module,exports){
+var vec2 = require('../math/vec2');
+
+module.exports = Spring;
+
+/**
+ * A spring, connecting two bodies.
+ *
+ * @class Spring
+ * @constructor
+ * @param {Body} bodyA
+ * @param {Body} bodyB
+ * @param {Object} [options]
+ * @param {number} options.restLength A number > 0. Default: 1
+ * @param {number} options.stiffness A number >= 0. Default: 100
+ * @param {number} options.damping A number >= 0. Default: 1
+ * @param {Array} options.worldAnchorA Where to hook the spring to body A, in world coordinates.
+ * @param {Array} options.worldAnchorB
+ * @param {Array} options.localAnchorA Where to hook the spring to body A, in local body coordinates.
+ * @param {Array} options.localAnchorB
+ */
+function Spring(bodyA,bodyB,options){
+ options = options || {};
+
+ /**
+ * Rest length of the spring.
+ * @property restLength
+ * @type {number}
+ */
+ this.restLength = typeof(options.restLength)=="number" ? options.restLength : 1;
+
+ /**
+ * Stiffness of the spring.
+ * @property stiffness
+ * @type {number}
+ */
+ this.stiffness = options.stiffness || 100;
+
+ /**
+ * Damping of the spring.
+ * @property damping
+ * @type {number}
+ */
+ this.damping = options.damping || 1;
+
+ /**
+ * First connected body.
+ * @property bodyA
+ * @type {Body}
+ */
+ this.bodyA = bodyA;
+
+ /**
+ * Second connected body.
+ * @property bodyB
+ * @type {Body}
+ */
+ this.bodyB = bodyB;
+
+ /**
+ * Anchor for bodyA in local bodyA coordinates.
+ * @property localAnchorA
+ * @type {Array}
+ */
+ this.localAnchorA = vec2.fromValues(0,0);
+
+ /**
+ * Anchor for bodyB in local bodyB coordinates.
+ * @property localAnchorB
+ * @type {Array}
+ */
+ this.localAnchorB = vec2.fromValues(0,0);
+
+ if(options.localAnchorA) vec2.copy(this.localAnchorA, options.localAnchorA);
+ if(options.localAnchorB) vec2.copy(this.localAnchorB, options.localAnchorB);
+ if(options.worldAnchorA) this.setWorldAnchorA(options.worldAnchorA);
+ if(options.worldAnchorB) this.setWorldAnchorB(options.worldAnchorB);
+};
+
+/**
+ * Set the anchor point on body A, using world coordinates.
+ * @method setWorldAnchorA
+ * @param {Array} worldAnchorA
+ */
+Spring.prototype.setWorldAnchorA = function(worldAnchorA){
+ this.bodyA.toLocalFrame(this.localAnchorA, worldAnchorA);
+};
+
+/**
+ * Set the anchor point on body B, using world coordinates.
+ * @method setWorldAnchorB
+ * @param {Array} worldAnchorB
+ */
+Spring.prototype.setWorldAnchorB = function(worldAnchorB){
+ this.bodyB.toLocalFrame(this.localAnchorB, worldAnchorB);
+};
+
+/**
+ * Get the anchor point on body A, in world coordinates.
+ * @method getWorldAnchorA
+ * @param {Array} result The vector to store the result in.
+ */
+Spring.prototype.getWorldAnchorA = function(result){
+ this.bodyA.toWorldFrame(result, this.localAnchorA);
+};
+
+/**
+ * Get the anchor point on body B, in world coordinates.
+ * @method getWorldAnchorB
+ * @param {Array} result The vector to store the result in.
+ */
+Spring.prototype.getWorldAnchorB = function(result){
+ this.bodyB.toWorldFrame(result, this.localAnchorB);
+};
+
+var applyForce_r = vec2.create(),
+ applyForce_r_unit = vec2.create(),
+ applyForce_u = vec2.create(),
+ applyForce_f = vec2.create(),
+ applyForce_worldAnchorA = vec2.create(),
+ applyForce_worldAnchorB = vec2.create(),
+ applyForce_ri = vec2.create(),
+ applyForce_rj = vec2.create(),
+ applyForce_tmp = vec2.create();
+
+/**
+ * Apply the spring force to the connected bodies.
+ * @method applyForce
+ */
+Spring.prototype.applyForce = function(){
+ var k = this.stiffness,
+ d = this.damping,
+ l = this.restLength,
+ bodyA = this.bodyA,
+ bodyB = this.bodyB,
+ r = applyForce_r,
+ r_unit = applyForce_r_unit,
+ u = applyForce_u,
+ f = applyForce_f,
+ tmp = applyForce_tmp;
+
+ var worldAnchorA = applyForce_worldAnchorA,
+ worldAnchorB = applyForce_worldAnchorB,
+ ri = applyForce_ri,
+ rj = applyForce_rj;
+
+ // Get world anchors
+ this.getWorldAnchorA(worldAnchorA);
+ this.getWorldAnchorB(worldAnchorB);
+
+ // Get offset points
+ vec2.sub(ri, worldAnchorA, bodyA.position);
+ vec2.sub(rj, worldAnchorB, bodyB.position);
+
+ // Compute distance vector between world anchor points
+ vec2.sub(r, worldAnchorB, worldAnchorA);
+ var rlen = vec2.len(r);
+ vec2.normalize(r_unit,r);
+
+ //console.log(rlen)
+ //console.log("A",vec2.str(worldAnchorA),"B",vec2.str(worldAnchorB))
+
+ // Compute relative velocity of the anchor points, u
+ vec2.sub(u, bodyB.velocity, bodyA.velocity);
+ vec2.crossZV(tmp, bodyB.angularVelocity, rj);
+ vec2.add(u, u, tmp);
+ vec2.crossZV(tmp, bodyA.angularVelocity, ri);
+ vec2.sub(u, u, tmp);
+
+ // F = - k * ( x - L ) - D * ( u )
+ vec2.scale(f, r_unit, -k*(rlen-l) - d*vec2.dot(u,r_unit));
+
+ // Add forces to bodies
+ vec2.sub( bodyA.force, bodyA.force, f);
+ vec2.add( bodyB.force, bodyB.force, f);
+
+ // Angular force
+ var ri_x_f = vec2.crossLength(ri, f);
+ var rj_x_f = vec2.crossLength(rj, f);
+ bodyA.angularForce -= ri_x_f;
+ bodyB.angularForce += rj_x_f;
+};
+
+},{"../math/vec2":33}],36:[function(require,module,exports){
+// Export p2 classes
+module.exports = {
+ AABB : require('./collision/AABB'),
+ AngleLockEquation : require('./equations/AngleLockEquation'),
+ Body : require('./objects/Body'),
+ Broadphase : require('./collision/Broadphase'),
+ Capsule : require('./shapes/Capsule'),
+ Circle : require('./shapes/Circle'),
+ Constraint : require('./constraints/Constraint'),
+ ContactEquation : require('./equations/ContactEquation'),
+ ContactMaterial : require('./material/ContactMaterial'),
+ Convex : require('./shapes/Convex'),
+ DistanceConstraint : require('./constraints/DistanceConstraint'),
+ Equation : require('./equations/Equation'),
+ EventEmitter : require('./events/EventEmitter'),
+ FrictionEquation : require('./equations/FrictionEquation'),
+ GearConstraint : require('./constraints/GearConstraint'),
+ GridBroadphase : require('./collision/GridBroadphase'),
+ GSSolver : require('./solver/GSSolver'),
+ Island : require('./solver/IslandSolver'),
+ IslandSolver : require('./solver/IslandSolver'),
+ Line : require('./shapes/Line'),
+ LockConstraint : require('./constraints/LockConstraint'),
+ Material : require('./material/Material'),
+ NaiveBroadphase : require('./collision/NaiveBroadphase'),
+ Particle : require('./shapes/Particle'),
+ Plane : require('./shapes/Plane'),
+ RevoluteConstraint : require('./constraints/RevoluteConstraint'),
+ PrismaticConstraint : require('./constraints/PrismaticConstraint'),
+ Rectangle : require('./shapes/Rectangle'),
+ RotationalVelocityEquation : require('./equations/RotationalVelocityEquation'),
+ SAP1DBroadphase : require('./collision/SAP1DBroadphase'),
+ Shape : require('./shapes/Shape'),
+ Solver : require('./solver/Solver'),
+ Spring : require('./objects/Spring'),
+ Utils : require('./utils/Utils'),
+ World : require('./world/World'),
+ QuadTree : require('./collision/QuadTree').QuadTree,
+ vec2 : require('./math/vec2'),
+ version : require('../package.json').version,
+};
+
+},{"../package.json":8,"./collision/AABB":9,"./collision/Broadphase":10,"./collision/GridBroadphase":11,"./collision/NaiveBroadphase":12,"./collision/QuadTree":14,"./collision/SAP1DBroadphase":15,"./constraints/Constraint":16,"./constraints/DistanceConstraint":17,"./constraints/GearConstraint":18,"./constraints/LockConstraint":19,"./constraints/PrismaticConstraint":20,"./constraints/RevoluteConstraint":21,"./equations/AngleLockEquation":22,"./equations/ContactEquation":23,"./equations/Equation":24,"./equations/FrictionEquation":25,"./equations/RotationalVelocityEquation":27,"./events/EventEmitter":28,"./material/ContactMaterial":29,"./material/Material":30,"./math/vec2":33,"./objects/Body":34,"./objects/Spring":35,"./shapes/Capsule":37,"./shapes/Circle":38,"./shapes/Convex":39,"./shapes/Line":40,"./shapes/Particle":41,"./shapes/Plane":42,"./shapes/Rectangle":43,"./shapes/Shape":44,"./solver/GSSolver":45,"./solver/IslandSolver":47,"./solver/Solver":48,"./utils/Utils":49,"./world/World":50}],37:[function(require,module,exports){
+var Shape = require('./Shape')
+, vec2 = require('../math/vec2')
+
+module.exports = Capsule;
+
+/**
+ * Capsule shape class.
+ * @class Capsule
+ * @constructor
+ * @extends {Shape}
+ * @param {Number} length The distance between the end points
+ * @param {Number} radius Radius of the capsule
+ */
+function Capsule(length,radius){
+ this.length = length || 1;
+ this.radius = radius || 1;
+
+ Shape.call(this,Shape.CAPSULE);
+};
+Capsule.prototype = new Shape();
+
+/**
+ * Compute the mass moment of inertia of the Capsule.
+ * @method conputeMomentOfInertia
+ * @param {Number} mass
+ * @return {Number}
+ * @todo
+ */
+Capsule.prototype.computeMomentOfInertia = function(mass){
+ // Approximate with rectangle
+ var r = this.radius,
+ w = this.length + r, // 2*r is too much, 0 is too little
+ h = r*2;
+ return mass * (h*h + w*w) / 12;
+};
+
+Capsule.prototype.updateBoundingRadius = function(){
+ this.boundingRadius = this.radius + this.length/2;
+};
+
+Capsule.prototype.updateArea = function(){
+ this.area = Math.PI * this.radius * this.radius + this.radius * 2 * this.length;
+};
+
+var r = vec2.create();
+
+/**
+ * @method computeAABB
+ * @param {AABB} out The resulting AABB.
+ * @param {Array} position
+ * @param {Number} angle
+ */
+Capsule.prototype.computeAABB = function(out, position, angle){
+ var radius = this.radius;
+
+ // Compute center position of one of the the circles, world oriented, but with local offset
+ vec2.set(r,this.length,0);
+ vec2.rotate(r,r,angle);
+
+ // Get bounds
+ vec2.set(out.upperBound, Math.max(r[0]+radius, -r[0]+radius),
+ Math.max(r[1]+radius, -r[1]+radius));
+ vec2.set(out.lowerBound, Math.min(r[0]-radius, -r[0]-radius),
+ Math.min(r[1]-radius, -r[1]-radius));
+
+ // Add offset
+ vec2.add(out.lowerBound, out.lowerBound, position);
+ vec2.add(out.upperBound, out.upperBound, position);
+};
+
+},{"../math/vec2":33,"./Shape":44}],38:[function(require,module,exports){
+var Shape = require('./Shape')
+, vec2 = require('../math/vec2')
+
+module.exports = Circle;
+
+/**
+ * Circle shape class.
+ * @class Circle
+ * @extends {Shape}
+ * @constructor
+ * @param {number} radius The radius of this circle
+ */
+function Circle(radius){
+
+ /**
+ * The radius of the circle.
+ * @property radius
+ * @type {number}
+ */
+ this.radius = radius || 1;
+
+ Shape.call(this,Shape.CIRCLE);
+};
+Circle.prototype = new Shape();
+Circle.prototype.computeMomentOfInertia = function(mass){
+ var r = this.radius;
+ return mass * r * r / 2;
+};
+
+Circle.prototype.updateBoundingRadius = function(){
+ this.boundingRadius = this.radius;
+};
+
+Circle.prototype.updateArea = function(){
+ this.area = Math.PI * this.radius * this.radius;
+};
+
+/**
+ * @method computeAABB
+ * @param {AABB} out The resulting AABB.
+ * @param {Array} position
+ * @param {Number} angle
+ */
+Circle.prototype.computeAABB = function(out, position, angle){
+ var r = this.radius;
+ vec2.set(out.upperBound, r, r);
+ vec2.set(out.lowerBound, -r, -r);
+ if(position){
+ vec2.add(out.lowerBound, out.lowerBound, position);
+ vec2.add(out.upperBound, out.upperBound, position);
+ }
+};
+
+},{"../math/vec2":33,"./Shape":44}],39:[function(require,module,exports){
+var Shape = require('./Shape')
+, vec2 = require('../math/vec2')
+, polyk = require('../math/polyk')
+, decomp = require('poly-decomp')
+
+module.exports = Convex;
+
+/**
+ * Convex shape class.
+ * @class Convex
+ * @constructor
+ * @extends {Shape}
+ * @param {Array} vertices An array of Float32Array vertices that span this shape. Vertices are given in counter-clockwise (CCW) direction.
+ */
+function Convex(vertices){
+
+ /**
+ * Vertices defined in the local frame.
+ * @property vertices
+ * @type {Array}
+ */
+ this.vertices = vertices || [];
+
+ // Copy the verts
+ for(var i=0; i h=2*a/b
+
+ // Get mass for the triangle
+ var m = area_triangle * density;
+
+ // Get inertia for this triangle: http://answers.yahoo.com/question/index?qid=20080721030038AA3oE1m
+ var I_triangle = m*(base * (Math.pow(height,3))) / 36;
+
+ // Add to total inertia using parallel axis theorem
+ var r2 = vec2.squaredLength(centroid);
+ I += I_triangle + m*r2;
+ }
+
+ return I;
+};
+
+/**
+ * Updates the .boundingRadius property
+ * @method updateBoundingRadius
+ */
+Convex.prototype.updateBoundingRadius = function(){
+ var verts = this.vertices,
+ r2 = 0;
+
+ for(var i=0; i!==verts.length; i++){
+ var l2 = vec2.squaredLength(verts[i]);
+ if(l2 > r2) r2 = l2;
+ }
+
+ this.boundingRadius = Math.sqrt(r2);
+};
+
+/**
+ * Update the .area
+ * @method updateArea
+ */
+Convex.prototype.updateArea = function(){
+ this.updateTriangles();
+ this.area = 0;
+
+ var triangles = this.triangles,
+ verts = this.vertices;
+ for(var i=0; i!==triangles.length; i++){
+ var t = triangles[i],
+ a = verts[t[0]],
+ b = verts[t[1]],
+ c = verts[t[2]];
+
+ // Get mass for the triangle (density=1 in this case)
+ // http://math.stackexchange.com/questions/80198/area-of-triangle-via-vectors
+ var m = decomp.Point.area(a,b,c)
+ this.area += m;
+ }
+};
+
+/**
+ * @method computeAABB
+ * @param {AABB} out
+ * @param {Array} position
+ * @param {Number} angle
+ */
+Convex.prototype.computeAABB = function(out, position, angle){
+ out.setFromPoints(this.vertices,position,angle);
+};
+
+},{"../math/polyk":32,"../math/vec2":33,"./Shape":44,"poly-decomp":7}],40:[function(require,module,exports){
+var Shape = require('./Shape')
+, vec2 = require('../math/vec2')
+
+module.exports = Line;
+
+/**
+ * Line shape class. The line shape is along the x direction, and stretches from [-length/2, 0] to [length/2,0].
+ * @class Line
+ * @param {Number} length The total length of the line
+ * @extends {Shape}
+ * @constructor
+ */
+function Line(length){
+
+ /**
+ * Length of this line
+ * @property length
+ * @type {Number}
+ */
+ this.length = length;
+
+ Shape.call(this,Shape.LINE);
+};
+Line.prototype = new Shape();
+Line.prototype.computeMomentOfInertia = function(mass){
+ return mass * Math.pow(this.length,2) / 12;
+};
+
+Line.prototype.updateBoundingRadius = function(){
+ this.boundingRadius = this.length/2;
+};
+
+var points = [vec2.create(),vec2.create()];
+
+/**
+ * @method computeAABB
+ * @param {AABB} out The resulting AABB.
+ * @param {Array} position
+ * @param {Number} angle
+ */
+Line.prototype.computeAABB = function(out, position, angle){
+ var l = this.length;
+ vec2.set(points[0], -l/2, 0);
+ vec2.set(points[1], l/2, 0);
+ out.setFromPoints(points,position,angle);
+};
+
+
+},{"../math/vec2":33,"./Shape":44}],41:[function(require,module,exports){
+var Shape = require('./Shape');
+
+module.exports = Particle;
+
+/**
+ * Particle shape class.
+ * @class Particle
+ * @constructor
+ * @extends {Shape}
+ */
+function Particle(){
+ Shape.call(this,Shape.PARTICLE);
+};
+Particle.prototype = new Shape();
+Particle.prototype.computeMomentOfInertia = function(mass){
+ return 0; // Can't rotate a particle
+};
+
+Particle.prototype.updateBoundingRadius = function(){
+ this.boundingRadius = 0;
+};
+
+/**
+ * @method computeAABB
+ * @param {AABB} out
+ * @param {Array} position
+ * @param {Number} angle
+ */
+Particle.prototype.computeAABB = function(out, position, angle){
+ var l = this.length;
+ vec2.copy(out.lowerBound, position);
+ vec2.copy(out.upperBound, position);
+};
+
+},{"./Shape":44}],42:[function(require,module,exports){
+var Shape = require('./Shape')
+, vec2 = require('../math/vec2')
+, Utils = require('../utils/Utils')
+
+module.exports = Plane;
+
+/**
+ * Plane shape class. The plane is facing in the Y direction.
+ * @class Plane
+ * @extends {Shape}
+ * @constructor
+ */
+function Plane(){
+ Shape.call(this,Shape.PLANE);
+};
+Plane.prototype = new Shape();
+
+/**
+ * Compute moment of inertia
+ * @method computeMomentOfInertia
+ */
+Plane.prototype.computeMomentOfInertia = function(mass){
+ return 0; // Plane is infinite. The inertia should therefore be infinty but by convention we set 0 here
+};
+
+/**
+ * Update the bounding radius
+ * @method updateBoundingRadius
+ */
+Plane.prototype.updateBoundingRadius = function(){
+ this.boundingRadius = Number.MAX_VALUE;
+};
+
+/**
+ * @method computeAABB
+ * @param {AABB} out
+ * @param {Array} position
+ * @param {Number} angle
+ */
+Plane.prototype.computeAABB = function(out, position, angle){
+ var a = 0,
+ set = vec2.set;
+ if(typeof(angle) == "number")
+ a = angle % (2*Math.PI);
+
+ if(a == 0){
+ // y goes from -inf to 0
+ set(out.lowerBound, -Number.MAX_VALUE, -Number.MAX_VALUE);
+ set(out.upperBound, Number.MAX_VALUE, 0);
+ } else if(a == Math.PI / 2){
+ // x goes from 0 to inf
+ set(out.lowerBound, 0, -Number.MAX_VALUE);
+ set(out.upperBound, Number.MAX_VALUE, Number.MAX_VALUE);
+ } else if(a == Math.PI){
+ // y goes from 0 to inf
+ set(out.lowerBound, -Number.MAX_VALUE, 0);
+ set(out.upperBound, Number.MAX_VALUE, Number.MAX_VALUE);
+ } else if(a == 3*Math.PI/2){
+ // x goes from -inf to 0
+ set(out.lowerBound, -Number.MAX_VALUE, -Number.MAX_VALUE);
+ set(out.upperBound, 0, Number.MAX_VALUE);
+ } else {
+ // Set max bounds
+ set(out.lowerBound, -Number.MAX_VALUE, -Number.MAX_VALUE);
+ set(out.upperBound, Number.MAX_VALUE, Number.MAX_VALUE);
+ }
+
+ vec2.add(out.lowerBound, out.lowerBound, position);
+ vec2.add(out.upperBound, out.upperBound, position);
+};
+
+Plane.prototype.updateArea = function(){
+ this.area = Number.MAX_VALUE;
+};
+
+
+},{"../math/vec2":33,"../utils/Utils":49,"./Shape":44}],43:[function(require,module,exports){
+var vec2 = require('../math/vec2')
+, Shape = require('./Shape')
+, Convex = require('./Convex')
+
+module.exports = Rectangle;
+
+/**
+ * Rectangle shape class.
+ * @class Rectangle
+ * @constructor
+ * @param {Number} w Width
+ * @param {Number} h Height
+ * @extends {Convex}
+ */
+function Rectangle(w,h){
+ var verts = [ vec2.fromValues(-w/2, -h/2),
+ vec2.fromValues( w/2, -h/2),
+ vec2.fromValues( w/2, h/2),
+ vec2.fromValues(-w/2, h/2)];
+
+ /**
+ * Total width of the rectangle
+ * @property width
+ * @type {Number}
+ */
+ this.width = w;
+
+ /**
+ * Total height of the rectangle
+ * @property height
+ * @type {Number}
+ */
+ this.height = h;
+
+ Convex.call(this,verts);
+};
+Rectangle.prototype = new Convex();
+
+/**
+ * Compute moment of inertia
+ * @method computeMomentOfInertia
+ * @param {Number} mass
+ * @return {Number}
+ */
+Rectangle.prototype.computeMomentOfInertia = function(mass){
+ var w = this.width,
+ h = this.height;
+ return mass * (h*h + w*w) / 12;
+};
+
+/**
+ * Update the bounding radius
+ * @method updateBoundingRadius
+ */
+Rectangle.prototype.updateBoundingRadius = function(){
+ var w = this.width,
+ h = this.height;
+ this.boundingRadius = Math.sqrt(w*w + h*h) / 2;
+};
+
+var corner1 = vec2.create(),
+ corner2 = vec2.create(),
+ corner3 = vec2.create(),
+ corner4 = vec2.create();
+
+/**
+ * @method computeAABB
+ * @param {AABB} out The resulting AABB.
+ * @param {Array} position
+ * @param {Number} angle
+ */
+Rectangle.prototype.computeAABB = function(out, position, angle){
+ /*
+ // Get world corners
+ vec2.rotate(corner1,this.vertices[0],angle);
+ vec2.rotate(corner2,this.vertices[1],angle);
+ vec2.rotate(corner3,this.vertices[2],angle);
+ vec2.rotate(corner4,this.vertices[3],angle);
+ vec2.set(out.upperBound, Math.max(corner1[0],corner2[0],corner3[0],corner4[0]),
+ Math.max(corner1[1],corner2[1],corner3[1],corner4[1]));
+ vec2.set(out.lowerBound, Math.min(corner1[0],corner2[0],corner3[0],corner4[0]),
+ Math.min(corner1[1],corner2[1],corner3[1],corner4[1]));
+
+ // Add world offset
+ vec2.add(out.lowerBound, out.lowerBound, position);
+ vec2.add(out.upperBound, out.upperBound, position);
+ */
+ out.setFromPoints(this.vertices,position,angle);
+};
+
+Rectangle.prototype.updateArea = function(){
+ this.area = this.width * this.height;
+};
+
+
+},{"../math/vec2":33,"./Convex":39,"./Shape":44}],44:[function(require,module,exports){
+module.exports = Shape;
+
+/**
+ * Base class for shapes.
+ * @class Shape
+ * @constructor
+ */
+function Shape(type){
+ this.type = type;
+
+ /**
+ * Shape object identifier.
+ * @type {Number}
+ * @property id
+ */
+ this.id = Shape.idCounter++;
+
+ /**
+ * Bounding circle radius of this shape
+ * @property boundingRadius
+ * @type {Number}
+ */
+ this.boundingRadius = 0;
+
+ /**
+ * Collision group that this shape belongs to (bit mask). See this tutorial.
+ * @property collisionGroup
+ * @type {Number}
+ * @example
+ * // Setup bits for each available group
+ * var PLAYER = Math.pow(2,0),
+ * ENEMY = Math.pow(2,1),
+ * GROUND = Math.pow(2,2)
+ *
+ * // Put shapes into their groups
+ * player1Shape.collisionGroup = PLAYER;
+ * player2Shape.collisionGroup = PLAYER;
+ * enemyShape .collisionGroup = ENEMY;
+ * groundShape .collisionGroup = GROUND;
+ *
+ * // Assign groups that each shape collide with.
+ * // Note that the players can collide with ground and enemies, but not with other players.
+ * player1Shape.collisionMask = ENEMY | GROUND;
+ * player2Shape.collisionMask = ENEMY | GROUND;
+ * enemyShape .collisionMask = PLAYER | GROUND;
+ * groundShape .collisionMask = PLAYER | ENEMY;
+ *
+ * @example
+ * // How collision check is done
+ * if(shapeA.collisionGroup & shapeB.collisionMask)!=0 && (shapeB.collisionGroup & shapeA.collisionMask)!=0){
+ * // The shapes will collide
+ * }
+ */
+ this.collisionGroup = 1;
+
+ /**
+ * Collision mask of this shape. See .collisionGroup.
+ * @property collisionMask
+ * @type {Number}
+ */
+ this.collisionMask = 1;
+ if(type) this.updateBoundingRadius();
+
+ /**
+ * Material to use in collisions for this Shape. If this is set to null, the world will use default material properties instead.
+ * @property material
+ * @type {Material}
+ */
+ this.material = null;
+
+ /**
+ * Area of this shape.
+ * @property area
+ * @type {Number}
+ */
+ this.area = 0;
+
+ this.updateArea();
+};
+
+Shape.idCounter = 0;
+
+Shape.CIRCLE = 1;
+Shape.PARTICLE = 2;
+Shape.PLANE = 4;
+Shape.CONVEX = 8;
+Shape.LINE = 16;
+Shape.RECTANGLE = 32;
+Shape.CAPSULE = 64;
+
+/**
+ * Should return the moment of inertia around the Z axis of the body given the total mass. See Wikipedia's list of moments of inertia.
+ * @method computeMomentOfInertia
+ * @param {Number} mass
+ * @return {Number} If the inertia is infinity or if the object simply isn't possible to rotate, return 0.
+ */
+Shape.prototype.computeMomentOfInertia = function(mass){
+ throw new Error("Shape.computeMomentOfInertia is not implemented in this Shape...");
+};
+
+/**
+ * Returns the bounding circle radius of this shape.
+ * @method updateBoundingRadius
+ * @return {Number}
+ */
+Shape.prototype.updateBoundingRadius = function(){
+ throw new Error("Shape.updateBoundingRadius is not implemented in this Shape...");
+};
+
+/**
+ * Update the .area property of the shape.
+ * @method updateArea
+ */
+Shape.prototype.updateArea = function(){
+ // To be implemented in all subclasses
+};
+
+/**
+ * Compute the world axis-aligned bounding box (AABB) of this shape.
+ * @method computeAABB
+ * @param {AABB} out The resulting AABB.
+ * @param {Array} position
+ * @param {Number} angle
+ */
+Shape.prototype.computeAABB = function(out, position, angle){
+ // To be implemented in each subclass
+};
+
+},{}],45:[function(require,module,exports){
+var vec2 = require('../math/vec2')
+, Solver = require('./Solver')
+, Utils = require('../utils/Utils')
+, FrictionEquation = require('../equations/FrictionEquation')
+
+module.exports = GSSolver;
+
+/**
+ * Iterative Gauss-Seidel constraint equation solver.
+ *
+ * @class GSSolver
+ * @constructor
+ * @extends Solver
+ * @param {Object} [options]
+ * @param {Number} options.iterations
+ * @param {Number} options.timeStep
+ * @param {Number} options.stiffness
+ * @param {Number} options.relaxation
+ * @param {Number} options.tolerance
+ */
+function GSSolver(options){
+ Solver.call(this,options);
+ options = options || {};
+
+ /**
+ * The number of iterations to do when solving. More gives better results, but is more expensive.
+ * @property iterations
+ * @type {Number}
+ */
+ this.iterations = options.iterations || 10;
+
+ /**
+ * The error tolerance. If the total error is below this limit, the solver will stop. Set to zero for as good solution as possible.
+ * @property tolerance
+ * @type {Number}
+ */
+ this.tolerance = options.tolerance || 0;
+
+ this.debug = options.debug || false;
+ this.arrayStep = 30;
+ this.lambda = new Utils.ARRAY_TYPE(this.arrayStep);
+ this.Bs = new Utils.ARRAY_TYPE(this.arrayStep);
+ this.invCs = new Utils.ARRAY_TYPE(this.arrayStep);
+
+ /**
+ * Whether to use .stiffness and .relaxation parameters from the Solver instead of each Equation individually.
+ * @type {Boolean}
+ * @property useGlobalEquationParameters
+ */
+ this.useGlobalEquationParameters = true;
+
+ /**
+ * Global equation stiffness. Larger number gives harder contacts, etc, but may also be more expensive to compute, or it will make your simulation explode.
+ * @property stiffness
+ * @type {Number}
+ */
+ this.stiffness = 1e6;
+
+ /**
+ * Global equation relaxation. This is the number of timesteps required for a constraint to be resolved. Larger number will give softer contacts. Set to around 3 or 4 for good enough results.
+ * @property relaxation
+ * @type {Number}
+ */
+ this.relaxation = 4;
+
+ /**
+ * Set to true to set all right hand side terms to zero when solving. Can be handy for a few applications.
+ * @property useZeroRHS
+ * @type {Boolean}
+ */
+ this.useZeroRHS = false;
+
+ /**
+ * Number of friction iterations to skip. If .skipFrictionIterations=2, then no FrictionEquations will be iterated until the third iteration.
+ * @property skipFrictionIterations
+ * @type {Number}
+ */
+ this.skipFrictionIterations = 0;
+};
+GSSolver.prototype = new Solver();
+
+function setArrayZero(array){
+ for(var i=0; i!==array.length; i++){
+ array[i] = 0.0;
+ }
+}
+
+/**
+ * Solve the system of equations
+ * @method solve
+ * @param {Number} h Time step
+ * @param {World} world World to solve
+ */
+GSSolver.prototype.solve = function(h,world){
+
+ this.sortEquations();
+
+ var iter = 0,
+ maxIter = this.iterations,
+ skipFrictionIter = this.skipFrictionIterations,
+ tolSquared = this.tolerance*this.tolerance,
+ equations = this.equations,
+ Neq = equations.length,
+ bodies = world.bodies,
+ Nbodies = world.bodies.length,
+ d = this.relaxation,
+ k = this.stiffness,
+ eps = 4.0 / (h * h * k * (1 + 4 * d)),
+ a = 4.0 / (h * (1 + 4 * d)),
+ b = (4.0 * d) / (1 + 4 * d),
+ useGlobalParams = this.useGlobalEquationParameters,
+ add = vec2.add,
+ set = vec2.set,
+ useZeroRHS = this.useZeroRHS,
+ lambda = this.lambda;
+
+ // Things that does not change during iteration can be computed once
+ if(lambda.length < Neq){
+ lambda = this.lambda = new Utils.ARRAY_TYPE(Neq + this.arrayStep);
+ this.Bs = new Utils.ARRAY_TYPE(Neq + this.arrayStep);
+ this.invCs = new Utils.ARRAY_TYPE(Neq + this.arrayStep);
+ } else {
+ setArrayZero(lambda);
+ }
+ var invCs = this.invCs,
+ Bs = this.Bs,
+ lambda = this.lambda;
+ if(!useGlobalParams){
+ for(var i=0, c; c = equations[i]; i++){
+ if(h !== c.h) c.updateSpookParams(h);
+ Bs[i] = c.computeB(c.a,c.b,h);
+ invCs[i] = c.computeInvC(c.eps);
+ }
+ } else {
+ for(var i=0, c; c = equations[i]; i++){
+ Bs[i] = c.computeB(a,b,h);
+ invCs[i] = c.computeInvC(eps);
+ }
+ }
+
+ var q, B, c, deltalambdaTot,i,j;
+
+ if(Neq !== 0){
+
+ // Reset vlambda
+ for(i=0; i!==Nbodies; i++){
+ bodies[i].resetConstraintVelocity();
+ }
+
+ // Iterate over equations
+ for(iter=0; iter!==maxIter; iter++){
+
+ // Accumulate the total error for each iteration.
+ deltalambdaTot = 0.0;
+
+ for(j=0; j!==Neq; j++){
+ c = equations[j];
+
+ if(c instanceof FrictionEquation && iter < skipFrictionIter)
+ continue;
+
+ var _eps = useGlobalParams ? eps : c.eps;
+
+ var deltalambda = GSSolver.iterateEquation(j,c,_eps,Bs,invCs,lambda,useZeroRHS,h);
+ deltalambdaTot += Math.abs(deltalambda);
+ }
+
+ // If the total error is small enough - stop iterate
+ if(deltalambdaTot*deltalambdaTot <= tolSquared) break;
+ }
+
+ // Add result to velocity
+ for(i=0; i!==Nbodies; i++){
+ bodies[i].addConstraintVelocity();
+ }
+ }
+};
+
+GSSolver.iterateEquation = function(j,eq,eps,Bs,invCs,lambda,useZeroRHS,dt){
+ // Compute iteration
+ var B = Bs[j],
+ invC = invCs[j],
+ lambdaj = lambda[j],
+ GWlambda = eq.computeGWlambda(eps);
+
+ if(eq instanceof FrictionEquation){
+ // Rescale the max friction force according to the normal force
+ eq.maxForce = eq.contactEquation.multiplier * eq.frictionCoefficient * dt;
+ eq.minForce = -eq.contactEquation.multiplier * eq.frictionCoefficient * dt;
+ }
+
+ var maxForce = eq.maxForce,
+ minForce = eq.minForce;
+
+ if(useZeroRHS) B = 0;
+
+ var deltalambda = invC * ( B - GWlambda - eps * lambdaj );
+
+ // Clamp if we are not within the min/max interval
+ var lambdaj_plus_deltalambda = lambdaj + deltalambda;
+ if(lambdaj_plus_deltalambda < minForce){
+ deltalambda = minForce - lambdaj;
+ } else if(lambdaj_plus_deltalambda > maxForce){
+ deltalambda = maxForce - lambdaj;
+ }
+ lambda[j] += deltalambda;
+ eq.multiplier = lambda[j] / dt;
+ eq.addToWlambda(deltalambda);
+
+ return deltalambda;
+};
+
+},{"../equations/FrictionEquation":25,"../math/vec2":33,"../utils/Utils":49,"./Solver":48}],46:[function(require,module,exports){
+module.exports = Island;
+
+/**
+ * An island of bodies connected with equations.
+ * @class Island
+ * @constructor
+ */
+function Island(){
+
+ /**
+ * Current equations in this island.
+ * @property equations
+ * @type {Array}
+ */
+ this.equations = [];
+
+ /**
+ * Current bodies in this island.
+ * @property bodies
+ * @type {Array}
+ */
+ this.bodies = [];
+}
+
+/**
+ * Clean this island from bodies and equations.
+ * @method reset
+ */
+Island.prototype.reset = function(){
+ this.equations.length = this.bodies.length = 0;
+}
+
+
+/**
+ * Get all unique bodies in this island.
+ * @method getBodies
+ * @return {Array} An array of Body
+ */
+Island.prototype.getBodies = function(){
+ var bodies = [],
+ bodyIds = [],
+ eqs = this.equations;
+ for(var i=0; i!==eqs.length; i++){
+ var eq = eqs[i];
+ if(bodyIds.indexOf(eq.bi.id)===-1){
+ bodies.push(eq.bi);
+ bodyIds.push(eq.bi.id);
+ }
+ if(bodyIds.indexOf(eq.bj.id)===-1){
+ bodies.push(eq.bj);
+ bodyIds.push(eq.bj.id);
+ }
+ }
+ return bodies;
+};
+
+/**
+ * Solves all constraints in the group of islands.
+ * @method solve
+ * @param {Number} dt
+ * @param {Solver} solver
+ */
+Island.prototype.solve = function(dt,solver){
+ var bodies = [];
+
+ solver.removeAllEquations();
+
+ // Add equations to solver
+ var numEquations = this.equations.length;
+ for(var j=0; j!==numEquations; j++){
+ solver.addEquation(this.equations[j]);
+ }
+ var islandBodies = this.getBodies();
+ var numBodies = islandBodies.length;
+ for(var j=0; j!==numBodies; j++){
+ bodies.push(islandBodies[j]);
+ }
+
+ // Solve
+ solver.solve(dt,{bodies:bodies});
+};
+
+},{}],47:[function(require,module,exports){
+var Solver = require('./Solver')
+, vec2 = require('../math/vec2')
+, Island = require('../solver/Island')
+, Body = require('../objects/Body')
+, STATIC = Body.STATIC
+
+module.exports = IslandSolver;
+
+/**
+ * Splits the system of bodies and equations into independent islands
+ *
+ * @class IslandSolver
+ * @constructor
+ * @param {Solver} subsolver
+ * @param {Object} options
+ * @extends Solver
+ */
+function IslandSolver(subsolver,options){
+ Solver.call(this,options);
+ var that = this;
+
+ /**
+ * The solver used in the workers.
+ * @property subsolver
+ * @type {Solver}
+ */
+ this.subsolver = subsolver;
+
+ /**
+ * Number of islands. Read only.
+ * @property numIslands
+ * @type {number}
+ */
+ this.numIslands = 0;
+
+ // Pooling of node objects saves some GC load
+ this._nodePool = [];
+
+ this._islandPool = [];
+
+ /**
+ * Fires before an island is solved.
+ * @event beforeSolveIsland
+ * @param {Island} island
+ */
+ this.beforeSolveIslandEvent = {
+ type : "beforeSolveIsland",
+ island : null,
+ };
+};
+IslandSolver.prototype = new Solver();
+
+function getUnvisitedNode(nodes){
+ var Nnodes = nodes.length;
+ for(var i=0; i!==Nnodes; i++){
+ var node = nodes[i];
+ if(!node.visited && !(node.body.motionState & STATIC)){ // correct?
+ return node;
+ }
+ }
+ return false;
+}
+
+function visitFunc(node,bds,eqs){
+ bds.push(node.body);
+ var Neqs = node.eqs.length;
+ for(var i=0; i!==Neqs; i++){
+ var eq = node.eqs[i];
+ if(eqs.indexOf(eq) === -1){
+ eqs.push(eq);
+ }
+ }
+}
+
+var queue = [];
+function bfs(root,visitFunc,bds,eqs){
+ queue.length = 0;
+ queue.push(root);
+ root.visited = true;
+ visitFunc(root,bds,eqs);
+ while(queue.length) {
+ var node = queue.pop();
+ // Loop over unvisited child nodes
+ var child;
+ while((child = getUnvisitedNode(node.children))) {
+ child.visited = true;
+ visitFunc(child,bds,eqs);
+ queue.push(child);
+ }
+ }
+}
+
+var tmpArray = [],
+ tmpArray2 = [],
+ tmpArray3 = [],
+ tmpArray4 = [];
+
+/**
+ * Solves the full system.
+ * @method solve
+ * @param {Number} dt
+ * @param {World} world
+ */
+IslandSolver.prototype.solve = function(dt,world){
+ var nodes = tmpArray,
+ bodies=world.bodies,
+ equations=this.equations,
+ Neq=equations.length,
+ Nbodies=bodies.length,
+ subsolver=this.subsolver,
+ workers = this._workers,
+ workerData = this._workerData,
+ workerIslandGroups = this._workerIslandGroups,
+ islandPool = this._islandPool;
+
+ tmpArray.length = 0;
+
+ // Create needed nodes, reuse if possible
+ for(var i=0; i!==Nbodies; i++){
+ if(this._nodePool.length)
+ nodes.push( this._nodePool.pop() );
+ else {
+ nodes.push({
+ body:bodies[i],
+ children:[],
+ eqs:[],
+ visited:false
+ });
+ }
+ }
+
+ // Reset node values
+ for(var i=0; i!==Nbodies; i++){
+ var node = nodes[i];
+ node.body = bodies[i];
+ node.children.length = 0;
+ node.eqs.length = 0;
+ node.visited = false;
+ }
+
+ // Add connectivity data. Each equation connects 2 bodies.
+ for(var k=0; k!==Neq; k++){
+ var eq=equations[k],
+ i=bodies.indexOf(eq.bi),
+ j=bodies.indexOf(eq.bj),
+ ni=nodes[i],
+ nj=nodes[j];
+ ni.children.push(nj);
+ ni.eqs.push(eq);
+ nj.children.push(ni);
+ nj.eqs.push(eq);
+ }
+
+ // The BFS search algorithm needs a traversal function. What we do is gather all bodies and equations connected.
+ var child, n=0, eqs=tmpArray2, bds=tmpArray3;
+ eqs.length = 0;
+ bds.length = 0;
+
+ // Get islands
+ var islands = tmpArray4;
+ islands.length = 0;
+ while((child = getUnvisitedNode(nodes))){
+ var island = islandPool.length ? islandPool.pop() : new Island();
+ eqs.length = 0;
+ bds.length = 0;
+ bfs(child,visitFunc,bds,eqs); // run search algo to gather an island of bodies
+
+ // Add equations to island
+ var Neqs = eqs.length;
+ for(var i=0; i!==Neqs; i++){
+ var eq = eqs[i];
+ island.equations.push(eq);
+ }
+
+ n++;
+ islands.push(island);
+ }
+
+ this.numIslands = n;
+
+ // Solve islands
+ var e = this.beforeSolveIslandEvent;
+ for(var i=0; ithis for an explanation.
+ * @method appendArray
+ * @static
+ * @param {Array} a
+ * @param {Array} b
+ */
+Utils.appendArray = function(a,b){
+ if (b.length < 150000) {
+ a.push.apply(a, b)
+ } else {
+ for (var i = 0, len = b.length; i !== len; ++i) {
+ a.push(b[i]);
+ }
+ }
+};
+
+/**
+ * Garbage free Array.splice(). Does not allocate a new array.
+ * @method splice
+ * @static
+ * @param {Array} array
+ * @param {Number} index
+ * @param {Number} howmany
+ */
+Utils.splice = function(array,index,howmany){
+ howmany = howmany || 1;
+ for (var i=index, len=array.length-howmany; i < len; i++)
+ array[i] = array[i + howmany];
+ array.length = len;
+};
+
+/**
+ * The array type to use for internal numeric computations.
+ * @type {Array}
+ * @static
+ * @property ARRAY_TYPE
+ */
+Utils.ARRAY_TYPE = Float32Array || Array;
+
+},{}],50:[function(require,module,exports){
+var GSSolver = require('../solver/GSSolver')
+, NaiveBroadphase = require('../collision/NaiveBroadphase')
+, vec2 = require('../math/vec2')
+, Circle = require('../shapes/Circle')
+, Rectangle = require('../shapes/Rectangle')
+, Convex = require('../shapes/Convex')
+, Line = require('../shapes/Line')
+, Plane = require('../shapes/Plane')
+, Capsule = require('../shapes/Capsule')
+, Particle = require('../shapes/Particle')
+, EventEmitter = require('../events/EventEmitter')
+, Body = require('../objects/Body')
+, Spring = require('../objects/Spring')
+, Material = require('../material/Material')
+, ContactMaterial = require('../material/ContactMaterial')
+, DistanceConstraint = require('../constraints/DistanceConstraint')
+, LockConstraint = require('../constraints/LockConstraint')
+, RevoluteConstraint = require('../constraints/RevoluteConstraint')
+, PrismaticConstraint = require('../constraints/PrismaticConstraint')
+, pkg = require('../../package.json')
+, Broadphase = require('../collision/Broadphase')
+, Narrowphase = require('../collision/Narrowphase')
+, Utils = require('../utils/Utils')
+
+module.exports = World;
+
+var currentVersion = pkg.version.split(".").slice(0,2).join("."); // "X.Y"
+
+if(typeof performance === 'undefined')
+ performance = {};
+if(!performance.now){
+ var nowOffset = Date.now();
+ if (performance.timing && performance.timing.navigationStart){
+ nowOffset = performance.timing.navigationStart
+ }
+ performance.now = function(){
+ return Date.now() - nowOffset;
+ }
+}
+
+/**
+ * The dynamics world, where all bodies and constraints lives.
+ *
+ * @class World
+ * @constructor
+ * @param {Object} [options]
+ * @param {Solver} options.solver Defaults to GSSolver.
+ * @param {Float32Array} options.gravity Defaults to [0,-9.78]
+ * @param {Broadphase} options.broadphase Defaults to NaiveBroadphase
+ * @extends {EventEmitter}
+ */
+function World(options){
+ EventEmitter.apply(this);
+
+ options = options || {};
+
+ /**
+ * All springs in the world.
+ *
+ * @property springs
+ * @type {Array}
+ */
+ this.springs = [];
+
+ /**
+ * All bodies in the world.
+ *
+ * @property bodies
+ * @type {Array}
+ */
+ this.bodies = [];
+
+ /**
+ * The solver used to satisfy constraints and contacts.
+ *
+ * @property solver
+ * @type {Solver}
+ */
+ this.solver = options.solver || new GSSolver();
+
+ /**
+ * The narrowphase to use to generate contacts.
+ *
+ * @property narrowphase
+ * @type {Narrowphase}
+ */
+ this.narrowphase = new Narrowphase(this);
+
+ /**
+ * Gravity in the world. This is applied on all bodies in the beginning of each step().
+ *
+ * @property
+ * @type {Float32Array}
+ */
+ this.gravity = options.gravity || vec2.fromValues(0, -9.78);
+
+ /**
+ * Whether to do timing measurements during the step() or not.
+ *
+ * @property doPofiling
+ * @type {Boolean}
+ */
+ this.doProfiling = options.doProfiling || false;
+
+ /**
+ * How many millisecconds the last step() took. This is updated each step if .doProfiling is set to true.
+ *
+ * @property lastStepTime
+ * @type {Number}
+ */
+ this.lastStepTime = 0.0;
+
+ /**
+ * The broadphase algorithm to use.
+ *
+ * @property broadphase
+ * @type {Broadphase}
+ */
+ this.broadphase = options.broadphase || new NaiveBroadphase();
+
+ this.broadphase.setWorld(this);
+
+ /**
+ * User-added constraints.
+ *
+ * @property constraints
+ * @type {Array}
+ */
+ this.constraints = [];
+
+ /**
+ * Friction between colliding bodies. This value is used if no matching ContactMaterial is found for a Material pair.
+ * @property defaultFriction
+ * @type {Number}
+ */
+ this.defaultFriction = 0.3;
+
+ /**
+ * Default coefficient of restitution between colliding bodies. This value is used if no matching ContactMaterial is found for a Material pair.
+ * @property defaultRestitution
+ * @type {Number}
+ */
+ this.defaultRestitution = 0.0;
+
+ /**
+ * For keeping track of what time step size we used last step
+ * @property lastTimeStep
+ * @type {Number}
+ */
+ this.lastTimeStep = 1/60;
+
+ /**
+ * Enable to automatically apply spring forces each step.
+ * @property applySpringForces
+ * @type {Boolean}
+ */
+ this.applySpringForces = true;
+
+ /**
+ * Enable to automatically apply body damping each step.
+ * @property applyDamping
+ * @type {Boolean}
+ */
+ this.applyDamping = true;
+
+ /**
+ * Enable to automatically apply gravity each step.
+ * @property applyGravity
+ * @type {Boolean}
+ */
+ this.applyGravity = true;
+
+ /**
+ * Enable/disable constraint solving in each step.
+ * @property solveConstraints
+ * @type {Boolean}
+ */
+ this.solveConstraints = true;
+
+ /**
+ * The ContactMaterials added to the World.
+ * @property contactMaterials
+ * @type {Array}
+ */
+ this.contactMaterials = [];
+
+ /**
+ * World time.
+ * @property time
+ * @type {Number}
+ */
+ this.time = 0.0;
+
+ this.fixedStepTime = 0.0;
+
+ /**
+ * Set to true if you want to the world to emit the "impact" event. Turning this off could improve performance.
+ * @property emitImpactEvent
+ * @type {Boolean}
+ */
+ this.emitImpactEvent = true;
+
+ /**
+ * Set to true if you want to the world to emit the "separation" event. Turning this off could improve performance.
+ * @property emitSeparationEvent
+ * @type {Boolean}
+ */
+ this.emitSeparationEvent = true;
+
+ // Id counters
+ this._constraintIdCounter = 0;
+ this._bodyIdCounter = 0;
+
+ /**
+ * Fired after the step().
+ * @event postStep
+ */
+ this.postStepEvent = {
+ type : "postStep",
+ };
+
+ /**
+ * @event addBody
+ * @param {Body} body
+ */
+ this.addBodyEvent = {
+ type : "addBody",
+ body : null
+ };
+
+ /**
+ * @event removeBody
+ * @param {Body} body
+ */
+ this.removeBodyEvent = {
+ type : "removeBody",
+ body : null
+ };
+
+ /**
+ * Fired when a spring is added to the world.
+ * @event addSpring
+ * @param {Spring} spring
+ */
+ this.addSpringEvent = {
+ type : "addSpring",
+ spring : null,
+ };
+
+ /**
+ * Fired when a first contact is created between two bodies. This event is fired after the step has been done.
+ * @event impact
+ * @param {Body} bodyA
+ * @param {Body} bodyB
+ */
+ this.impactEvent = {
+ type: "impact",
+ bodyA : null,
+ bodyB : null,
+ shapeA : null,
+ shapeB : null,
+ contactEquation : null,
+ };
+
+ /**
+ * Fired when two bodies stop touching. This event is fired during the narrowphase.
+ * @event impact
+ * @param {Body} bodyA
+ * @param {Body} bodyB
+ */
+ this.separationEvent = {
+ type: "separation",
+ bodyA : null,
+ bodyB : null,
+ };
+
+ /**
+ * Enable / disable automatic body sleeping
+ * @property allowSleep
+ * @type {Boolean}
+ */
+ this.enableBodySleeping = false;
+};
+World.prototype = new Object(EventEmitter.prototype);
+
+/**
+ * Add a constraint to the simulation.
+ *
+ * @method addConstraint
+ * @param {Constraint} c
+ */
+World.prototype.addConstraint = function(c){
+ this.constraints.push(c);
+};
+
+/**
+ * Add a ContactMaterial to the simulation.
+ * @method addContactMaterial
+ * @param {ContactMaterial} contactMaterial
+ */
+World.prototype.addContactMaterial = function(contactMaterial){
+ this.contactMaterials.push(contactMaterial);
+};
+
+/**
+ * Removes a contact material
+ *
+ * @method removeContactMaterial
+ * @param {ContactMaterial} cm
+ */
+World.prototype.removeContactMaterial = function(cm){
+ var idx = this.contactMaterials.indexOf(cm);
+ if(idx!==-1)
+ Utils.splice(this.contactMaterials,idx,1);
+};
+
+/**
+ * Get a contact material given two materials
+ * @method getContactMaterial
+ * @param {Material} materialA
+ * @param {Material} materialB
+ * @return {ContactMaterial} The matching ContactMaterial, or false on fail.
+ * @todo Use faster hash map to lookup from material id's
+ */
+World.prototype.getContactMaterial = function(materialA,materialB){
+ var cmats = this.contactMaterials;
+ for(var i=0, N=cmats.length; i!==N; i++){
+ var cm = cmats[i];
+ if( (cm.materialA === materialA) && (cm.materialB === materialB) ||
+ (cm.materialA === materialB) && (cm.materialB === materialA) )
+ return cm;
+ }
+ return false;
+};
+
+/**
+ * Removes a constraint
+ *
+ * @method removeConstraint
+ * @param {Constraint} c
+ */
+World.prototype.removeConstraint = function(c){
+ var idx = this.constraints.indexOf(c);
+ if(idx!==-1){
+ Utils.splice(this.constraints,idx,1);
+ }
+};
+
+var step_r = vec2.create(),
+ step_runit = vec2.create(),
+ step_u = vec2.create(),
+ step_f = vec2.create(),
+ step_fhMinv = vec2.create(),
+ step_velodt = vec2.create(),
+ step_mg = vec2.create(),
+ xiw = vec2.fromValues(0,0),
+ xjw = vec2.fromValues(0,0),
+ zero = vec2.fromValues(0,0);
+
+/**
+ * Step the physics world forward in time.
+ *
+ * There are two modes. The simple mode is fixed timestepping without interpolation. In this case you only use the first argument. The second case uses interpolation. In that you also provide the time since the function was last used, as well as the maximum fixed timesteps to take.
+ *
+ * @method step
+ * @param {Number} dt The fixed time step size to use.
+ * @param {Number} [timeSinceLastCalled=0] The time elapsed since the function was last called.
+ * @param {Number} [maxSubSteps=10] Maximum number of fixed steps to take per function call.
+ *
+ * @example
+ * // fixed timestepping without interpolation
+ * var world = new World();
+ * world.step(0.01);
+ */
+World.prototype.step = function(dt,timeSinceLastCalled,maxSubSteps){
+ maxSubSteps = maxSubSteps || 10;
+ timeSinceLastCalled = timeSinceLastCalled || 0;
+
+ if(timeSinceLastCalled == 0){ // Fixed, simple stepping
+
+ this.internalStep(dt);
+
+ // Increment time
+ this.time += dt;
+
+ } else {
+
+ var internalSteps = Math.floor( (this.time+timeSinceLastCalled) / dt) - Math.floor(this.time / dt);
+ internalSteps = Math.min(internalSteps,maxSubSteps);
+
+ for(var i=0; i0){
+ World.integrateBody(body,dt);
+ }
+ }
+
+ // Reset force
+ for(var i=0; i!==Nbodies; i++){
+ bodies[i].setZeroForce();
+ }
+
+ if(doProfiling){
+ t1 = performance.now();
+ that.lastStepTime = t1-t0;
+ }
+
+ // Emit impact event
+ if(this.emitImpactEvent){
+ var ev = this.impactEvent;
+ for(var i=0; i!==np.contactEquations.length; i++){
+ var eq = np.contactEquations[i];
+ if(eq.firstImpact){
+ ev.bodyA = eq.bi;
+ ev.bodyB = eq.bj;
+ ev.shapeA = eq.shapeA;
+ ev.shapeB = eq.shapeB;
+ ev.contactEquation = eq;
+ this.emit(ev);
+ }
+ }
+ }
+
+ // Sleeping update
+ if(this.enableBodySleeping){
+ for(i=0; i!==Nbodies; i++){
+ bodies[i].sleepTick(this.time);
+ }
+ }
+
+ this.emit(this.postStepEvent);
+};
+
+var ib_fhMinv = vec2.create();
+var ib_velodt = vec2.create();
+
+/**
+ * Move a body forward in time.
+ * @static
+ * @method integrateBody
+ * @param {Body} body
+ * @param {Number} dt
+ */
+World.integrateBody = function(body,dt){
+ var minv = body.invMass,
+ f = body.force,
+ pos = body.position,
+ velo = body.velocity;
+
+ // Angular step
+ if(!body.fixedRotation){
+ body.angularVelocity += body.angularForce * body.invInertia * dt;
+ body.angle += body.angularVelocity * dt;
+ }
+
+ // Linear step
+ vec2.scale(ib_fhMinv,f,dt*minv);
+ vec2.add(velo,ib_fhMinv,velo);
+ vec2.scale(ib_velodt,velo,dt);
+ vec2.add(pos,pos,ib_velodt);
+
+ body.aabbNeedsUpdate = true;
+};
+
+/**
+ * Runs narrowphase for the shape pair i and j.
+ * @static
+ * @method runNarrowphase
+ * @param {Narrowphase} np
+ * @param {Body} bi
+ * @param {Shape} si
+ * @param {Array} xi
+ * @param {Number} ai
+ * @param {Body} bj
+ * @param {Shape} sj
+ * @param {Array} xj
+ * @param {Number} aj
+ * @param {Number} mu
+ */
+World.runNarrowphase = function(np,bi,si,xi,ai,bj,sj,xj,aj,mu,restitution){
+
+ if(!((si.collisionGroup & sj.collisionMask) !== 0 && (sj.collisionGroup & si.collisionMask) !== 0))
+ return;
+
+ var reducedMass = bi.invMass + bj.invMass;
+ if(reducedMass > 0)
+ reducedMass = 1/reducedMass;
+
+ // Get world position and angle of each shape
+ vec2.rotate(xiw, xi, bi.angle);
+ vec2.rotate(xjw, xj, bj.angle);
+ vec2.add(xiw, xiw, bi.position);
+ vec2.add(xjw, xjw, bj.position);
+ var aiw = ai + bi.angle;
+ var ajw = aj + bj.angle;
+
+ // Run narrowphase
+ np.enableFriction = mu > 0;
+ np.frictionCoefficient = mu;
+ np.restitution = restitution;
+
+ var resolver = np[si.type | sj.type];
+ if (resolver) {
+ if (si.type < sj.type) {
+ resolver.call(np, bi,si,xiw,aiw, bj,sj,xjw,ajw);
+ } else {
+ resolver.call(np, bj,sj,xjw,ajw, bi,si,xiw,aiw);
+ }
+ }
+};
+
+/**
+ * Add a spring to the simulation
+ *
+ * @method addSpring
+ * @param {Spring} s
+ */
+World.prototype.addSpring = function(s){
+ this.springs.push(s);
+ this.addSpringEvent.spring = s;
+ this.emit(this.addSpringEvent);
+};
+
+/**
+ * Remove a spring
+ *
+ * @method removeSpring
+ * @param {Spring} s
+ */
+World.prototype.removeSpring = function(s){
+ var idx = this.springs.indexOf(s);
+ if(idx===-1)
+ Utils.splice(this.springs,idx,1);
+};
+
+/**
+ * Add a body to the simulation
+ *
+ * @method addBody
+ * @param {Body} body
+ *
+ * @example
+ * var world = new World(),
+ * body = new Body();
+ * world.addBody(body);
+ *
+ */
+World.prototype.addBody = function(body){
+ if(body.world)
+ throw new Error("This body is already added to a World.");
+
+ this.bodies.push(body);
+ body.world = this;
+ this.addBodyEvent.body = body;
+ this.emit(this.addBodyEvent);
+};
+
+/**
+ * Remove a body from the simulation
+ *
+ * @method removeBody
+ * @param {Body} body
+ */
+World.prototype.removeBody = function(body){
+ if(body.world !== this)
+ throw new Error("The body was never added to this World, cannot remove it.");
+
+ body.world = null;
+ var idx = this.bodies.indexOf(body);
+ if(idx!==-1){
+ Utils.splice(this.bodies,idx,1);
+ this.removeBodyEvent.body = body;
+ body.resetConstraintVelocity();
+ this.emit(this.removeBodyEvent);
+ }
+};
+
+/**
+ * Get a body by its id.
+ * @method getBodyById
+ * @return {Body|Boolean} The body, or false if it was not found.
+ */
+World.prototype.getBodyById = function(id){
+ var bodies = this.bodies;
+ for(var i=0; i=0; i--){
+ this.removeConstraint(cs[i]);
+ }
+
+ // Remove all bodies
+ var bodies = this.bodies;
+ for(var i=bodies.length-1; i>=0; i--){
+ this.removeBody(bodies[i]);
+ }
+
+ // Remove all springs
+ var springs = this.springs;
+ for(var i=springs.length-1; i>=0; i--){
+ this.removeSpring(springs[i]);
+ }
+
+ // Remove all contact materials
+ var cms = this.contactMaterials;
+ for(var i=cms.length-1; i>=0; i--){
+ this.removeContactMaterial(cms[i]);
+ }
+};
+
+/**
+ * Get a copy of this World instance
+ * @method clone
+ * @return {World}
+ */
+World.prototype.clone = function(){
+ var world = new World();
+ world.fromJSON(this.toJSON());
+ return world;
+};
+
+var hitTest_tmp1 = vec2.create(),
+ hitTest_zero = vec2.fromValues(0,0),
+ hitTest_tmp2 = vec2.fromValues(0,0);
+
+/**
+ * Test if a world point overlaps bodies
+ * @method hitTest
+ * @param {Array} worldPoint Point to use for intersection tests
+ * @param {Array} bodies A list of objects to check for intersection
+ * @param {Number} precision Used for matching against particles and lines. Adds some margin to these infinitesimal objects.
+ * @return {Array} Array of bodies that overlap the point
+ */
+World.prototype.hitTest = function(worldPoint,bodies,precision){
+ precision = precision || 0;
+
+ // Create a dummy particle body with a particle shape to test against the bodies
+ var pb = new Body({ position:worldPoint }),
+ ps = new Particle(),
+ px = worldPoint,
+ pa = 0,
+ x = hitTest_tmp1,
+ zero = hitTest_zero,
+ tmp = hitTest_tmp2;
+ pb.addShape(ps);
+
+ var n = this.narrowphase,
+ result = [];
+
+ // Check bodies
+ for(var i=0, N=bodies.length; i!==N; i++){
+ var b = bodies[i];
+ for(var j=0, NS=b.shapes.length; j!==NS; j++){
+ var s = b.shapes[j],
+ offset = b.shapeOffsets[j] || zero,
+ angle = b.shapeAngles[j] || 0.0;
+
+ // Get shape world position + angle
+ vec2.rotate(x, offset, b.angle);
+ vec2.add(x, x, b.position);
+ var a = angle + b.angle;
+
+ if( (s instanceof Circle && n.circleParticle (b,s,x,a, pb,ps,px,pa, true)) ||
+ (s instanceof Convex && n.particleConvex (pb,ps,px,pa, b,s,x,a, true)) ||
+ (s instanceof Plane && n.particlePlane (pb,ps,px,pa, b,s,x,a, true)) ||
+ (s instanceof Capsule && n.particleCapsule (pb,ps,px,pa, b,s,x,a, true)) ||
+ (s instanceof Particle && vec2.squaredLength(vec2.sub(tmp,x,worldPoint)) < precision*precision)
+ ){
+ result.push(b);
+ }
+ }
+ }
+
+ return result;
+};
+
+},{"../../package.json":8,"../collision/Broadphase":10,"../collision/NaiveBroadphase":12,"../collision/Narrowphase":13,"../constraints/DistanceConstraint":17,"../constraints/LockConstraint":19,"../constraints/PrismaticConstraint":20,"../constraints/RevoluteConstraint":21,"../events/EventEmitter":28,"../material/ContactMaterial":29,"../material/Material":30,"../math/vec2":33,"../objects/Body":34,"../objects/Spring":35,"../shapes/Capsule":37,"../shapes/Circle":38,"../shapes/Convex":39,"../shapes/Line":40,"../shapes/Particle":41,"../shapes/Plane":42,"../shapes/Rectangle":43,"../solver/GSSolver":45,"../utils/Utils":49}]},{},[36])
+(36)
+});
+;
\ No newline at end of file
diff --git a/examples/wip/index.php b/examples/wip/index.php
index f72e4b700..9bbc53f6f 100644
--- a/examples/wip/index.php
+++ b/examples/wip/index.php
@@ -75,6 +75,7 @@
+
phaser
diff --git a/examples/wip/p21.js b/examples/wip/p21.js
new file mode 100644
index 000000000..b2debdef4
--- /dev/null
+++ b/examples/wip/p21.js
@@ -0,0 +1,130 @@
+
+var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render }, false);
+
+function preload() {
+
+ game.load.image('box', 'assets/sprites/block.png');
+
+}
+
+var world;
+
+var box;
+var box2;
+
+var boxShape;
+var boxBody;
+
+var boxShape2;
+var boxBody2;
+
+var planeShape;
+var planeBody;
+
+function p2px(v) {
+ return v *= -20;
+}
+
+function px2p(v) {
+ return v * -0.05;
+}
+
+function create() {
+
+ game.renderer.roundPixels = true;
+
+ box = game.add.image(300, 200, 'box');
+ box.anchor.set(0.5);
+
+ box2 = game.add.image(300, 10, 'box');
+ box2.anchor.set(0.5);
+
+ world = new p2.World();
+
+ boxShape = new p2.Rectangle(px2p(box.width), px2p(box.height));
+ boxBody = new p2.Body({ mass: 1, position:[px2p(box.x), px2p(box.y)], angularVelocity: 1 });
+ boxBody.addShape(boxShape);
+
+ boxShape2 = new p2.Rectangle(px2p(box2.width), px2p(box2.height));
+ boxBody2 = new p2.Body({ mass: 1, position:[px2p(box2.x), px2p(box2.y)], angularVelocity: 1 });
+ boxBody2.addShape(boxShape2);
+
+
+ world.addBody(boxBody);
+ world.addBody(boxBody2);
+
+ // Add a plane
+ planeShape = new p2.Plane();
+ planeBody = new p2.Body({ mass: 0, position:[0, px2p(550)] });
+ // planeBody = new p2.Body();
+ planeBody.addShape(planeShape);
+ world.addBody(planeBody);
+
+}
+
+function update() {
+
+ // Move physics bodies forward in time
+ world.step(1/60);
+
+ box.x = p2px(boxBody.position[0]);
+ box.y = p2px(boxBody.position[1]);
+ box.rotation = boxBody.angle; // Rotate to the box body frame
+
+ box2.x = p2px(boxBody2.position[0]);
+ box2.y = p2px(boxBody2.position[1]);
+ box2.rotation = boxBody2.angle; // Rotate to the box body frame
+
+}
+
+function render() {
+
+ // game.debug.renderText('x: ' + p2px(boxBody.position[0]), 32, 32);
+ // game.debug.renderText('y: ' + p2px(boxBody.position[1]), 32, 64);
+ // game.debug.renderText('r: ' + boxBody.angle, 32, 96);
+
+ // drawbox();
+
+}
+
+function drawbox() {
+
+ var ctx = game.context;
+
+ ctx.save();
+ ctx.translate(game.width/2, game.height/2); // Translate to the center
+ ctx.scale(50, -50); // Zoom in and flip y axis
+
+ ctx.lineWidth = 0.05;
+ ctx.strokeStyle = 'rgb(255,255,255)';
+
+
+ ctx.beginPath();
+ var x = boxBody.position[0],
+ y = boxBody.position[1];
+ ctx.save();
+ ctx.translate(x, y); // Translate to the center of the box
+ ctx.rotate(boxBody.angle); // Rotate to the box body frame
+ ctx.rect(-boxShape.width/2, -boxShape.height/2, boxShape.width, boxShape.height);
+ ctx.stroke();
+ ctx.closePath();
+ // ctx.restore();
+
+ // ctx.save();
+ // ctx.translate(game.width/2, game.height/2); // Translate to the center
+ // ctx.scale(50, -50); // Zoom in and flip y axis
+
+ // ctx.lineWidth = 0.05;
+ // ctx.strokeStyle = 'rgb(255,255,255)';
+ // ctx.beginPath();
+
+ // var y = planeBody.position[1];
+ // ctx.rotate(0); // Rotate to the box body frame
+ // ctx.moveTo(-game.width, y);
+ // ctx.lineTo( game.width, y);
+ // ctx.stroke();
+
+ // ctx.closePath();
+ ctx.restore();
+}
+
diff --git a/examples/wip/p22.js b/examples/wip/p22.js
new file mode 100644
index 000000000..58d063c18
--- /dev/null
+++ b/examples/wip/p22.js
@@ -0,0 +1,94 @@
+
+var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render });
+
+function preload() {
+
+ game.load.image('box', 'assets/sprites/block.png');
+
+}
+
+var box;
+var box2;
+
+function p2px(v) {
+ return v *= -20;
+}
+
+function px2p(v) {
+ return v * -0.05;
+}
+
+function create() {
+
+ box = game.add.sprite(200, 0, 'box');
+
+ box2 = game.add.sprite(400, 0, 'box');
+ box2.physicsEnabled = true;
+ box2.body.mass = 2;
+
+ /*
+ // Add a plane
+ planeShape = new p2.Plane();
+ planeBody = new p2.Body({ mass: 0, position:[0, px2p(550)] });
+ // planeBody = new p2.Body();
+ planeBody.addShape(planeShape);
+ world.addBody(planeBody);
+ */
+
+}
+
+function update() {
+
+}
+
+function render() {
+
+ // game.debug.renderText('x: ' + p2px(boxBody.position[0]), 32, 32);
+ // game.debug.renderText('y: ' + p2px(boxBody.position[1]), 32, 64);
+ // game.debug.renderText('r: ' + boxBody.angle, 32, 96);
+
+ // drawbox();
+
+}
+
+function drawbox() {
+
+ var ctx = game.context;
+
+ ctx.save();
+ ctx.translate(game.width/2, game.height/2); // Translate to the center
+ ctx.scale(50, -50); // Zoom in and flip y axis
+
+ ctx.lineWidth = 0.05;
+ ctx.strokeStyle = 'rgb(255,255,255)';
+
+
+ ctx.beginPath();
+ var x = boxBody.position[0],
+ y = boxBody.position[1];
+ ctx.save();
+ ctx.translate(x, y); // Translate to the center of the box
+ ctx.rotate(boxBody.angle); // Rotate to the box body frame
+ ctx.rect(-boxShape.width/2, -boxShape.height/2, boxShape.width, boxShape.height);
+ ctx.stroke();
+ ctx.closePath();
+ // ctx.restore();
+
+ // ctx.save();
+ // ctx.translate(game.width/2, game.height/2); // Translate to the center
+ // ctx.scale(50, -50); // Zoom in and flip y axis
+
+ // ctx.lineWidth = 0.05;
+ // ctx.strokeStyle = 'rgb(255,255,255)';
+ // ctx.beginPath();
+
+ // var y = planeBody.position[1];
+ // ctx.rotate(0); // Rotate to the box body frame
+ // ctx.moveTo(-game.width, y);
+ // ctx.lineTo( game.width, y);
+ // ctx.stroke();
+
+ // ctx.closePath();
+ ctx.restore();
+}
+
diff --git a/examples/wip/pixi1.js b/examples/wip/pixi1.js
index 46403d4e7..626be83bf 100644
--- a/examples/wip/pixi1.js
+++ b/examples/wip/pixi1.js
@@ -16,9 +16,7 @@ function create() {
game.stage.backgroundColor = '#ff5500';
- game.renderer.useFillRect = false;
-
- sprite = game.add.sprite(0.5, 0, 'pic');
+ sprite = game.add.sprite(0, -200, 'pic');
// g = game.add.group(null, 'billy');
diff --git a/src/Phaser.js b/src/Phaser.js
index 1ce9b3a1b..684567559 100644
--- a/src/Phaser.js
+++ b/src/Phaser.js
@@ -42,6 +42,10 @@ var Phaser = Phaser || {
UP: 3,
DOWN: 4,
+ DYNAMIC: 1,
+ STATIC: 2,
+ KINEMATIC: 4,
+
CANVAS_PX_ROUND: false,
CANVAS_CLEAR_RECT: true
diff --git a/src/core/Game.js b/src/core/Game.js
index c7ddb6470..dfdfe8697 100644
--- a/src/core/Game.js
+++ b/src/core/Game.js
@@ -179,7 +179,8 @@ Phaser.Game = function (width, height, renderer, parent, state, transparent, ant
this.world = null;
/**
- * @property {Phaser.Physics.PhysicsManager} physics - Reference to the physics manager.
+ * // {Phaser.Physics.Arcade.ArcadePhysics} physics - Reference to the physics manager.
+ * @property {Phaser.Physics.World} physics - Reference to the physics world.
* @default
*/
this.physics = null;
@@ -446,7 +447,8 @@ Phaser.Game.prototype = {
this.tweens = new Phaser.TweenManager(this);
this.input = new Phaser.Input(this);
this.sound = new Phaser.SoundManager(this);
- this.physics = new Phaser.Physics.Arcade(this);
+ // this.physics = new Phaser.Physics.Arcade(this);
+ this.physics = new Phaser.Physics.World(this);
this.particles = new Phaser.Particles(this);
this.plugins = new Phaser.PluginManager(this, this);
this.net = new Phaser.Net(this);
@@ -614,6 +616,7 @@ Phaser.Game.prototype = {
this.tweens.update();
this.sound.update();
this.input.update();
+ this.physics.update();
this.state.update();
this.world.update();
this.particles.update();
diff --git a/src/gameobjects/Sprite.js b/src/gameobjects/Sprite.js
index 7739fbabf..a54cb14c4 100644
--- a/src/gameobjects/Sprite.js
+++ b/src/gameobjects/Sprite.js
@@ -112,9 +112,9 @@ Phaser.Sprite = function (game, x, y, key, frame) {
this.input = null;
/**
- * @property {Phaser.Physics.Arcade.Body} body - By default Sprites have a Phaser.Physics Body attached to them. You can operate physics actions via this property, or null it to skip all physics updates.
+ * @property {Phaser.Physics.Body|null} body - The Sprites physics Body. Will be null unless physics has been enabled via `Sprite.physicsEnabled = true`.
*/
- this.body = new Phaser.Physics.Arcade.Body(this);
+ this.body = null;
/**
* @property {number} health - Health value. Used in combination with damage() to allow for quick killing of Sprites.
@@ -193,13 +193,13 @@ Phaser.Sprite.prototype.preUpdate = function() {
this._cache[2] = this.rotation;
this._cache[4] = 0;
- if (this.body)
- {
- this.body.x = (this.world.x - (this.anchor.x * this.width)) + this.body.offset.x;
- this.body.y = (this.world.y - (this.anchor.y * this.height)) + this.body.offset.y;
- this.body.preX = this.body.x;
- this.body.preY = this.body.y;
- }
+ // if (this.body)
+ // {
+ // this.body.x = (this.world.x - (this.anchor.x * this.width)) + this.body.offset.x;
+ // this.body.y = (this.world.y - (this.anchor.y * this.height)) + this.body.offset.y;
+ // this.body.preX = this.body.x;
+ // this.body.preY = this.body.y;
+ // }
return false;
}
@@ -633,7 +633,7 @@ Phaser.Sprite.prototype.play = function (name, frameRate, loop, killOnComplete)
* If you wish to work in radians instead of degrees use the property Sprite.rotation instead. Working in radians is also a little faster as it doesn't have to convert the angle.
*
* @name Phaser.Sprite#angle
-* @property {number} angle - The angle of this Image in degrees.
+* @property {number} angle - The angle of this Sprite in degrees.
*/
Object.defineProperty(Phaser.Sprite.prototype, "angle", {
@@ -818,3 +818,38 @@ Object.defineProperty(Phaser.Sprite.prototype, "inputEnabled", {
}
});
+
+/**
+* By default Sprites won't add themselves to the physics world. By setting physicsEnabled to true a Physics Body is
+* attached to this Sprite and it will then start to process physics world updates. Access all of its properties via Sprite.body.
+*
+* @name Phaser.Sprite#physicsEnabled
+* @property {boolean} physicsEnabled - Set to true to add this Sprite to the physics world. Set to false to destroy the body and remove it from the physics world.
+*/
+Object.defineProperty(Phaser.Sprite.prototype, "physicsEnabled", {
+
+ get: function () {
+
+ return (this.body !== null);
+
+ },
+
+ set: function (value) {
+
+ if (value)
+ {
+ if (this.body === null)
+ {
+ this.body = new Phaser.Physics.Body(this);
+ }
+ }
+ else
+ {
+ if (this.body)
+ {
+ this.body.destroy();
+ }
+ }
+ }
+
+});
diff --git a/src/math/Math.js b/src/math/Math.js
index feec5c534..bb1c6f796 100644
--- a/src/math/Math.js
+++ b/src/math/Math.js
@@ -1280,6 +1280,28 @@ Phaser.Math = {
},
+ /**
+ * Convert p2 physics value to pixel scale.
+ *
+ * @method Phaser.Math#p2px
+ * @param {number} v - The value to convert.
+ * @return {number} The scaled value.
+ */
+ p2px: function (v) {
+ return v *= -20;
+ },
+
+ /**
+ * Convert pixel value to p2 physics scale.
+ *
+ * @method Phaser.Math#px2p
+ * @param {number} v - The value to convert.
+ * @return {number} The scaled value.
+ */
+ px2p: function (v) {
+ return v * -0.05;
+ },
+
/**
* Convert degrees to radians.
*
diff --git a/src/physics/Body.js b/src/physics/Body.js
new file mode 100644
index 000000000..8168b429c
--- /dev/null
+++ b/src/physics/Body.js
@@ -0,0 +1,710 @@
+/**
+* @author Richard Davey
+* @copyright 2014 Photon Storm Ltd.
+* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
+*/
+
+/**
+* The Physics Body is linked to a single Sprite and defines properties that determine how the physics body is simulated.
+* These properties affect how the body reacts to forces, what forces it generates on itself (to simulate friction), and how it reacts to collisions in the scene. In most cases, the properties are used to simulate physical effects.
+* Each body also has its own property values that determine exactly how it reacts to forces and collisions in the scene.
+*
+* @class Phaser.Physics.Body
+* @classdesc Physics Body Constructor
+* @constructor
+* @param {Phaser.Sprite} sprite - The Sprite object this physics body belongs to.
+*/
+Phaser.Physics.Body = function (sprite) {
+
+ /**
+ * @property {Phaser.Sprite} sprite - Reference to the parent Sprite.
+ */
+ this.sprite = sprite;
+
+ /**
+ * @property {Phaser.Game} game - Local reference to game.
+ */
+ this.game = sprite.game;
+
+ /**
+ * @property {Phaser.Point} offset - The offset of the Physics Body from the Sprite x/y position.
+ */
+ this.offset = new Phaser.Point();
+
+ this.shape = null;
+
+ // this.data = new p2.Body({ mass: 0, position:[this.px2p(sprite.x), this.px2p(sprite.y)], angularVelocity: 1 });
+ this.data = new p2.Body({ position:[this.px2p(sprite.x), this.px2p(sprite.y)] });
+
+ /**
+ * @property {number} _sx - Internal cache var.
+ * @private
+ */
+ this._sx = sprite.scale.x;
+
+ /**
+ * @property {number} _sy - Internal cache var.
+ * @private
+ */
+ this._sy = sprite.scale.y;
+
+ // Set-up the default shape
+ this.setRectangle(sprite.width, sprite.height, 0, 0);
+
+ this.game.physics.addBody(this.data);
+
+ // Set-up contact events
+ // this.sprite.events.onBeginContact = new Phaser.Signal();
+ // this.sprite.events.onEndContact = new Phaser.Signal();
+
+};
+
+Phaser.Physics.Body.prototype = {
+
+ /*
+ * Add a shape to the body. You can pass a local transform when adding a shape,
+ * so that the shape gets an offset and angle relative to the body center of mass.
+ * Will automatically update the mass properties and bounding radius.
+ *
+ * @method Phaser.Physics.Body#addShape
+ */
+ addShape: function (shape, offsetX, offsetY, angle) {
+
+ if (typeof offsetX === 'undefined') { offsetX = 0; }
+ if (typeof offsetY === 'undefined') { offsetY = 0; }
+ if (typeof angle === 'undefined') { angle = 0; }
+
+ return this.data.addShape(shape, [ this.px2p(offsetX), this.px2p(offsetY) ], angle);
+
+ },
+
+ /**
+ * Moves the shape offsets so their center of mass becomes the body center of mass.
+ *
+ * @method Phaser.Physics.Body#adjustCenterOfMass
+ */
+ adjustCenterOfMass: function () {
+
+ this.data.adjustCenterOfMass();
+
+ },
+
+ // applyDamping
+ // applyForce
+ // fromPolygon
+
+ /**
+ * Remove a shape from the Body.
+ *
+ * @method Phaser.Physics.Body#removeShape
+ */
+ removeShape: function (shape) {
+
+ return this.data.removeShape(shape);
+
+ },
+
+ /**
+ * Sets the force on the body to zero.
+ *
+ * @method Phaser.Physics.Body#setZeroForce
+ */
+ setZeroForce: function () {
+
+ this.data.setZeroForce();
+
+ },
+
+ // toLocalFrame
+ // toWorldFrame
+
+ /**
+ * Internal method that updates the Body scale in relation to the parent Sprite.
+ *
+ * @method Phaser.Physics.Body#updateScale
+ * @protected
+ */
+ updateScale: function () {
+
+ // if (this.polygon)
+ // {
+ // this.polygon.scale(this.sprite.scale.x / this._sx, this.sprite.scale.y / this._sy);
+ // }
+ // else
+ // {
+ // this.shape.r *= Math.max(this.sprite.scale.x, this.sprite.scale.y);
+ // }
+
+ this._sx = this.sprite.scale.x;
+ this._sy = this.sprite.scale.y;
+
+ },
+
+ /**
+ * Internal method that updates the Body position in relation to the parent Sprite.
+ *
+ * @method Phaser.Physics.Body#preUpdate
+ * @protected
+ */
+ preUpdate: function () {
+
+ // this.x = (this.sprite.world.x - (this.sprite.anchor.x * this.sprite.width)) + this.offset.x;
+ // this.y = (this.sprite.world.y - (this.sprite.anchor.y * this.sprite.height)) + this.offset.y;
+
+ // This covers any motion that happens during this frame, not since the last frame
+ // this.preX = this.x;
+ // this.preY = this.y;
+ // this.preRotation = this.sprite.angle;
+
+ // this.rotation = this.preRotation;
+
+ if (this.sprite.scale.x !== this._sx || this.sprite.scale.y !== this._sy)
+ {
+ this.updateScale();
+ }
+
+ },
+
+ /**
+ * Internal method. This is called directly before the sprites are sent to the renderer and after the update function has finished.
+ *
+ * @method Phaser.Physics.Body#postUpdate
+ * @protected
+ */
+ postUpdate: function () {
+
+ /*
+ if (this.moves)
+ {
+ this.game.physics.checkBounds(this);
+
+ this.reboundCheck(true, true, true);
+
+ this._dx = this.deltaX();
+ this._dy = this.deltaY();
+
+ if (this._dx < 0)
+ {
+ this.facing = Phaser.LEFT;
+ }
+ else if (this._dx > 0)
+ {
+ this.facing = Phaser.RIGHT;
+ }
+
+ if (this._dy < 0)
+ {
+ this.facing = Phaser.UP;
+ }
+ else if (this._dy > 0)
+ {
+ this.facing = Phaser.DOWN;
+ }
+
+ if (this._dx !== 0 || this._dy !== 0)
+ {
+ this.sprite.x += this._dx;
+ this.sprite.y += this._dy;
+ }
+
+ if (this.allowRotation && this.deltaZ() !== 0)
+ {
+ this.sprite.angle += this.deltaZ();
+ }
+
+ if (this.sprite.scale.x !== this._sx || this.sprite.scale.y !== this._sy)
+ {
+ this.updateScale();
+ }
+ }
+ */
+
+ this.sprite.x = this.p2px(this.data.position[0]);
+ this.sprite.y = this.p2px(this.data.position[1]);
+ this.sprite.rotation = this.data.angle;
+
+ },
+
+ /**
+ * Resets the Body motion values: velocity, acceleration, angularVelocity and angularAcceleration.
+ * Also resets the forces to defaults: gravity, bounce, minVelocity,maxVelocity, angularDrag, maxAngular, mass, friction and checkCollision if 'full' specified.
+ *
+ * @method Phaser.Physics.Body#reset
+ * @param {boolean} [full=false] - A full reset clears down settings you may have set, such as gravity, bounce and drag. A non-full reset just clears motion values.
+ */
+ reset: function (full) {
+
+ /*
+ if (typeof full === 'undefined') { full = false; }
+
+ if (full)
+ {
+ this.gravity.setTo(0, 0);
+ this.bounce.setTo(0, 0);
+ this.minVelocity.setTo(5, 5);
+ this.maxVelocity.setTo(1000, 1000);
+ this.angularDrag = 0;
+ this.maxAngular = 1000;
+ this.mass = 1;
+ this.friction = 0.0;
+ this.checkCollision = { none: false, any: true, up: true, down: true, left: true, right: true };
+ }
+
+ this.velocity.setTo(0, 0);
+ this.acceleration.setTo(0, 0);
+ this.angularVelocity = 0;
+ this.angularAcceleration = 0;
+ this.blocked = { x: 0, y: 0, up: false, down: false, left: false, right: false };
+ this.x = (this.sprite.world.x - (this.sprite.anchor.x * this.sprite.width)) + this.offset.x;
+ this.y = (this.sprite.world.y - (this.sprite.anchor.y * this.sprite.height)) + this.offset.y;
+ this.preX = this.x;
+ this.preY = this.y;
+ this.updateBounds();
+
+ this.contacts.length = 0;
+ */
+
+ },
+
+ /**
+ * Destroys this Body and all references it holds to other objects.
+ *
+ * @method Phaser.Physics.Body#destroy
+ */
+ destroy: function () {
+
+ this.sprite = null;
+
+ /*
+ this.collideCallback = null;
+ this.collideCallbackContext = null;
+
+ this.customSeparateCallback = null;
+ this.customSeparateContext = null;
+
+ this.contacts.length = 0;
+ */
+
+ },
+
+ /**
+ * Sets this Body to use a circle of the given radius for all collision.
+ * The Circle will be centered on the center of the Sprite by default, but can be adjusted via the Body.offset property and the setCircle x/y parameters.
+ *
+ * @method Phaser.Physics.Body#setCircle
+ * @param {number} radius - The radius of this circle (in pixels)
+ * @param {number} [offsetX=0] - The x amount the circle will be offset from the Sprites center.
+ * @param {number} [offsetY=0] - The y amount the circle will be offset from the Sprites center.
+ */
+ setCircle: function (radius, offsetX, offsetY) {
+
+ // if (typeof offsetX === 'undefined') { offsetX = this.sprite._cache.halfWidth; }
+ // if (typeof offsetY === 'undefined') { offsetY = this.sprite._cache.halfHeight; }
+
+ // this.type = Phaser.Physics.Arcade.CIRCLE;
+ // this.shape = new SAT.Circle(new SAT.Vector(this.sprite.x, this.sprite.y), radius);
+ // this.polygon = null;
+
+ // this.offset.setTo(offsetX, offsetY);
+
+ },
+
+ /**
+ * Sets this Body to use a rectangle for all collision.
+ * If you don't specify any parameters it will be sized to match the parent Sprites current width and height (including scale factor) and centered on the sprite.
+ *
+ * @method Phaser.Physics.Body#setRectangle
+ * @param {number} [width] - The width of the rectangle. If not specified it will default to the width of the parent Sprite.
+ * @param {number} [height] - The height of the rectangle. If not specified it will default to the height of the parent Sprite.
+ * @param {number} [translateX] - The x amount the rectangle will be translated from the Sprites center.
+ * @param {number} [translateY] - The y amount the rectangle will be translated from the Sprites center.
+ */
+ setRectangle: function (width, height, offsetX, offsetY) {
+
+ if (typeof width === 'undefined') { width = this.sprite.width; }
+ if (typeof height === 'undefined') { height = this.sprite.height; }
+ // if (typeof translateX === 'undefined') { translateX = this.sprite.width / 2; }
+ // if (typeof translateY === 'undefined') { translateY = this.sprite.height / 2; }
+
+ // This means 1 shape per body, need to move this to an array or similar
+ this.shape = new p2.Rectangle(this.px2p(width), this.px2p(height));
+ this.data.addShape(this.shape);
+
+ this.offset.setTo(0, 0);
+
+ },
+
+ /**
+ * Sets this Body to use a convex polygon for collision.
+ * The points are specified in a counter-clockwise direction and must create a convex polygon.
+ * Use Body.translate and/or Body.offset to re-position the polygon from the Sprite origin.
+ *
+ * @method Phaser.Physics.Body#setPolygon
+ * @param {(SAT.Vector[]|number[]|...SAT.Vector|...number)} points - This can be an array of Vectors that form the polygon,
+ * a flat array of numbers that will be interpreted as [x,y, x,y, ...], or the arguments passed can be
+ * all the points of the polygon e.g. `setPolygon(new SAT.Vector(), new SAT.Vector(), ...)`, or the
+ * arguments passed can be flat x,y values e.g. `setPolygon(x,y, x,y, x,y, ...)` where `x` and `y` are Numbers.
+ */
+ setPolygon: function (points) {
+
+ /*
+ this.type = Phaser.Physics.Arcade.POLYGON;
+ this.shape = null;
+
+ if (!Array.isArray(points))
+ {
+ points = Array.prototype.slice.call(arguments);
+ }
+
+ if (typeof points[0] === 'number')
+ {
+ var p = [];
+
+ for (var i = 0, len = points.length; i < len; i += 2)
+ {
+ p.push(new SAT.Vector(points[i], points[i + 1]));
+ }
+
+ points = p;
+ }
+
+ this.polygon = new SAT.Polygon(new SAT.Vector(this.sprite.center.x, this.sprite.center.y), points);
+
+ this.offset.setTo(0, 0);
+ */
+
+ },
+
+ /**
+ * Convert p2 physics value to pixel scale.
+ *
+ * @method Phaser.Math#p2px
+ * @param {number} v - The value to convert.
+ * @return {number} The scaled value.
+ */
+ p2px: function (v) {
+ return v *= -20;
+ },
+
+ /**
+ * Convert pixel value to p2 physics scale.
+ *
+ * @method Phaser.Math#px2p
+ * @param {number} v - The value to convert.
+ * @return {number} The scaled value.
+ */
+ px2p: function (v) {
+ return v * -0.05;
+ }
+
+};
+
+Phaser.Physics.Body.prototype.constructor = Phaser.Physics.Body;
+
+/**
+* @name Phaser.Physics.Body#allowSleep
+* @property {boolean} allowSleep -
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "allowSleep", {
+
+ get: function () {
+
+ return this.data.allowSleep;
+
+ },
+
+ set: function (value) {
+
+ if (value !== this.data.allowSleep)
+ {
+ this.data.allowSleep = value;
+ }
+
+ }
+
+});
+
+/**
+* The angle of the Body in degrees from its original orientation. Values from 0 to 180 represent clockwise rotation; values from 0 to -180 represent counterclockwise rotation.
+* Values outside this range are added to or subtracted from 360 to obtain a value within the range. For example, the statement Body.angle = 450 is the same as Body.angle = 90.
+* If you wish to work in radians instead of degrees use the property Body.rotation instead. Working in radians is faster as it doesn't have to convert values.
+*
+* @name Phaser.Physics.Body#angle
+* @property {number} angle - The angle of this Body in degrees.
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "angle", {
+
+ get: function() {
+
+ return Phaser.Math.wrapAngle(Phaser.Math.radToDeg(this.data.angle));
+
+ },
+
+ set: function(value) {
+
+ this.data.angle = Phaser.Math.degToRad(Phaser.Math.wrapAngle(value));
+
+ }
+
+});
+
+/**
+* Damping is specified as a value between 0 and 1, which is the proportion of velocity lost per second.
+* @name Phaser.Physics.Body#angularDamping
+* @property {number} angularDamping - The angular damping acting acting on the body.
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "angularDamping", {
+
+ get: function () {
+
+ this.data.angularDamping;
+
+ },
+
+ set: function (value) {
+
+ this.data.angularDamping = value;
+
+ }
+
+});
+
+/**
+* @name Phaser.Physics.Body#angularForce
+* @property {number} angularForce - The angular force acting on the body.
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "angularForce", {
+
+ get: function () {
+
+ this.data.angularForce;
+
+ },
+
+ set: function (value) {
+
+ this.data.angularForce = value;
+
+ }
+
+});
+
+/**
+* @name Phaser.Physics.Body#angularVelocity
+* @property {number} angularVelocity - The angular velocity of the body.
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "angularVelocity", {
+
+ get: function () {
+
+ this.data.angularVelocity;
+
+ },
+
+ set: function (value) {
+
+ this.data.angularVelocity = value;
+
+ }
+
+});
+
+/**
+* Damping is specified as a value between 0 and 1, which is the proportion of velocity lost per second.
+* @name Phaser.Physics.Body#damping
+* @property {number} damping - The linear damping acting on the body in the velocity direction.
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "damping", {
+
+ get: function () {
+
+ this.data.damping;
+
+ },
+
+ set: function (value) {
+
+ this.data.damping = value;
+
+ }
+
+});
+
+/**
+* @name Phaser.Physics.Body#fixedRotation
+* @property {boolean} fixedRotation -
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "fixedRotation", {
+
+ get: function () {
+
+ return this.data.fixedRotation;
+
+ },
+
+ set: function (value) {
+
+ if (value !== this.data.fixedRotation)
+ {
+ this.data.fixedRotation = value;
+ // update anything?
+ }
+
+ }
+
+});
+
+// force
+
+/**
+* @name Phaser.Physics.Body#inertia
+* @property {number} inertia - The inertia of the body around the Z axis..
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "inertia", {
+
+ get: function () {
+
+ this.data.inertia;
+
+ },
+
+ set: function (value) {
+
+ this.data.inertia = value;
+
+ }
+
+});
+
+/**
+* @name Phaser.Physics.Body#mass
+* @property {number} mass -
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "mass", {
+
+ get: function () {
+
+ return this.data.mass;
+
+ },
+
+ set: function (value) {
+
+ if (value !== this.data.mass)
+ {
+ this.data.mass = value;
+ this.data.updateMassProperties();
+ }
+
+ }
+
+});
+
+/**
+* @name Phaser.Physics.Body#motionState
+* @property {number} motionState - The type of motion this body has. Should be one of: Body.STATIC (the body does not move), Body.DYNAMIC (body can move and respond to collisions) and Body.KINEMATIC (only moves according to its .velocity).
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "motionState", {
+
+ get: function () {
+
+ return this.data.motionState;
+
+ },
+
+ set: function (value) {
+
+ if (value !== this.data.motionState)
+ {
+ this.data.motionState = value;
+ // update?
+ }
+
+ }
+
+});
+
+/**
+* The angle of the Body in radians.
+* If you wish to work in degrees instead of radians use the Body.angle property instead. Working in radians is faster as it doesn't have to convert values.
+*
+* @name Phaser.Physics.Body#rotation
+* @property {number} rotation - The angle of this Body in radians.
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "rotation", {
+
+ get: function() {
+
+ return this.data.angle;
+
+ },
+
+ set: function(value) {
+
+ this.data.angle = value;
+
+ }
+
+});
+
+/**
+* @name Phaser.Physics.Body#sleepSpeedLimit
+* @property {number} sleepSpeedLimit - .
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "sleepSpeedLimit", {
+
+ get: function () {
+
+ this.data.sleepSpeedLimit;
+
+ },
+
+ set: function (value) {
+
+ this.data.sleepSpeedLimit = value;
+
+ }
+
+});
+
+// velocity
+
+/**
+* @name Phaser.Physics.Body#x
+* @property {number} x - The x coordinate of this Body.
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "x", {
+
+ get: function () {
+
+ this.p2px(this.data.position[0]);
+
+ },
+
+ set: function (value) {
+
+ this.data.position[0] = this.px2p(value);
+
+ }
+
+});
+
+/**
+* @name Phaser.Physics.Body#y
+* @property {number} y - The y coordinate of this Body.
+*/
+Object.defineProperty(Phaser.Physics.Body.prototype, "y", {
+
+ get: function () {
+
+ this.p2px(this.data.position[1]);
+
+ },
+
+ set: function (value) {
+
+ this.data.position[1] = this.px2p(value);
+
+ }
+
+});
diff --git a/src/physics/World.js b/src/physics/World.js
new file mode 100644
index 000000000..a710c4cd4
--- /dev/null
+++ b/src/physics/World.js
@@ -0,0 +1,39 @@
+/**
+* @author Richard Davey
+* @copyright 2014 Photon Storm Ltd.
+* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
+*/
+
+/**
+* @class Phaser.Physics
+*/
+Phaser.Physics = {};
+
+/**
+* @class Phaser.Physics.World
+* @classdesc Physics World Constructor
+* @constructor
+* @param {Phaser.Game}
+*/
+Phaser.Physics.World = function (game) {
+
+ /**
+ * @property {Phaser.Game} game - Local reference to game.
+ */
+ this.game = game;
+
+ p2.World.call(this);
+
+};
+
+Phaser.Physics.World.prototype = Object.create(p2.World.prototype);
+Phaser.Physics.World.prototype.constructor = Phaser.Physics.World;
+
+/**
+* @method Phaser.Physics.World.prototype.update
+*/
+Phaser.Physics.World.prototype.update = function () {
+
+ this.step(1 / 60);
+
+};