/** * 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('p2', (function() { return this.p2 = 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; }; /** * Calculates 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 {vec2} 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] = m[0] * x + m[2] * y; out[1] = m[1] * x + m[3] * y; return out; }; /** * Transforms the vec2 with a mat2d * * @param {vec2} out the receiving vector * @param {vec2} a the vector to transform * @param {mat2d} m matrix to transform with * @returns {vec2} out */ vec2.transformMat2d = function(out, a, m) { var x = a[0], y = a[1]; out[0] = m[0] * x + m[2] * y + m[4]; out[1] = m[1] * x + m[3] * y + m[5]; return out; }; /** * Transforms the vec2 with a mat3 * 3rd vector component is implicitly '1' * * @param {vec2} out the receiving vector * @param {vec2} a the vector to transform * @param {mat3} m matrix to transform with * @returns {vec2} out */ vec2.transformMat3 = function(out, a, m) { var x = a[0], y = a[1]; out[0] = m[0] * x + m[3] * y + m[6]; out[1] = m[1] * x + m[4] * y + m[7]; return out; }; /** * Transforms the vec2 with a mat4 * 3rd vector component is implicitly '0' * 4th vector component is implicitly '1' * * @param {vec2} out the receiving vector * @param {vec2} a the vector to transform * @param {mat4} m matrix to transform with * @returns {vec2} out */ vec2.transformMat4 = function(out, a, m) { var x = a[0], y = a[1]; out[0] = m[0] * x + m[4] * y + m[12]; out[1] = m[1] * x + m[5] * y + m[13]; 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 = vec2.create(); 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; } },{}],2:[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":5}],3:[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; }; },{}],4:[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":2,"./Point":3,"./Scalar":5}],5:[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; }; },{}],6:[function(require,module,exports){ module.exports = { Polygon : require("./Polygon"), Point : require("./Point"), }; },{"./Point":3,"./Polygon":4}],7:[function(require,module,exports){ module.exports={ "name": "p2", "version": "0.5.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": { "grunt": "~0.4.0", "grunt-contrib-jshint": "~0.9.2", "grunt-contrib-nodeunit": "~0.1.2", "grunt-contrib-uglify": "~0.4.0", "grunt-contrib-watch": "~0.5.0", "grunt-browserify": "~2.0.1", "z-schema": "~2.4.6" }, "dependencies": { "poly-decomp": "0.1.0", "gl-matrix": "2.1.0" } } },{}],8:[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} [options.upperBound] * @param {Array} [options.lowerBound] */ function AABB(options){ /** * The lower bound of the bounding box. * @property lowerBound * @type {Array} */ this.lowerBound = vec2.create(); if(options && options.lowerBound){ vec2.copy(this.lowerBound, options.lowerBound); } /** * The upper bound of the bounding box. * @property upperBound * @type {Array} */ this.upperBound = vec2.create(); if(options && options.upperBound){ vec2.copy(this.upperBound, options.upperBound); } } var tmp = vec2.create(); /** * Set the AABB bounds from a set of points. * @method setFromPoints * @param {Array} points An array of vec2's. */ AABB.prototype.setFromPoints = function(points,position,angle){ var l = this.lowerBound, u = this.upperBound; vec2.set(l, Number.MAX_VALUE, Number.MAX_VALUE); vec2.set(u, -Number.MAX_VALUE, -Number.MAX_VALUE); for(var i=0; i u[j]){ u[j] = p[j]; } if(p[j] < l[j]){ l[j] = p[j]; } } } // Add offset if(position){ vec2.add(this.lowerBound, this.lowerBound, position); vec2.add(this.upperBound, this.upperBound, position); } }; /** * Copy bounds from an AABB to this AABB * @method copy * @param {AABB} aabb */ AABB.prototype.copy = function(aabb){ vec2.copy(this.lowerBound, aabb.lowerBound); vec2.copy(this.upperBound, aabb.upperBound); }; /** * Extend this AABB so that it covers the given AABB too. * @method extend * @param {AABB} aabb */ AABB.prototype.extend = function(aabb){ // Loop over x and y for(var i=0; i<2; i++){ // Extend lower bound if(aabb.lowerBound[i] < this.lowerBound[i]){ this.lowerBound[i] = aabb.lowerBound[i]; } // Upper if(aabb.upperBound[i] > this.upperBound[i]){ this.upperBound[i] = aabb.upperBound[i]; } } }; /** * Returns true if the given AABB overlaps this AABB. * @method overlaps * @param {AABB} aabb * @return {Boolean} */ AABB.prototype.overlaps = function(aabb){ var l1 = this.lowerBound, u1 = this.upperBound, l2 = aabb.lowerBound, u2 = aabb.upperBound; // l2 u2 // |---------| // |--------| // l1 u1 return ((l2[0] <= u1[0] && u1[0] <= u2[0]) || (l1[0] <= u2[0] && u2[0] <= u1[0])) && ((l2[1] <= u1[1] && u1[1] <= u2[1]) || (l1[1] <= u2[1] && u2[1] <= u1[1])); }; },{"../math/vec2":30,"../utils/Utils":47}],9:[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(type){ this.type = type; /** * 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} * @readOnly */ this.world = null; /** * The bounding volume type to use in the broadphase algorithms. * @property {Number} boundingVolumeType */ this.boundingVolumeType = Broadphase.AABB; } /** * Axis aligned bounding box type. * @static * @property {Number} AABB */ Broadphase.AABB = 1; /** * Bounding circle type. * @static * @property {Number} BOUNDING_CIRCLE */ Broadphase.BOUNDING_CIRCLE = 2; /** * 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 the bounding radius of two bodies overlap. * @method boundingRadiusCheck * @param {Body} bodyA * @param {Body} bodyB * @return {Boolean} */ Broadphase.prototype.boundingVolumeCheck = function(bodyA, bodyB){ var result; switch(this.boundingVolumeType){ case Broadphase.BOUNDING_CIRCLE: result = Broadphase.boundingRadiusCheck(bodyA,bodyB); break; case Broadphase.AABB: result = Broadphase.aabbCheck(bodyA,bodyB); break; default: throw new Error('Bounding volume type not recognized: '+this.boundingVolumeType); } return result; }; /** * 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 static vs kinematic bodies if( (bodyA.motionState === Body.KINEMATIC && bodyB.motionState === Body.STATIC) || (bodyA.motionState === Body.STATIC && bodyB.motionState === Body.KINEMATIC)){ return false; } // Cannot collide kinematic vs kinematic if(bodyA.motionState === Body.KINEMATIC && bodyB.motionState === Body.KINEMATIC){ return false; } // Cannot collide both sleeping bodies if(bodyA.sleepState === Body.SLEEPING && bodyB.sleepState === Body.SLEEPING){ return false; } // Cannot collide if one is static and the other is sleeping if( (bodyA.sleepState === Body.SLEEPING && bodyB.motionState === Body.STATIC) || (bodyB.sleepState === Body.SLEEPING && bodyA.motionState === Body.STATIC)){ return false; } return true; }; Broadphase.NAIVE = 1; Broadphase.SAP = 2; },{"../math/vec2":30,"../objects/Body":31}],10:[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') , Utils = require('../utils/Utils') module.exports = GridBroadphase; /** * Broadphase that uses axis-aligned bins. * @class GridBroadphase * @constructor * @extends Broadphase * @param {object} [options] * @param {number} [options.xmin] Lower x bound of the grid * @param {number} [options.xmax] Upper x bound * @param {number} [options.ymin] Lower y bound * @param {number} [options.ymax] Upper y bound * @param {number} [options.nx] Number of bins along x axis * @param {number} [options.ny] Number of bins along y axis * @todo Should have an option for dynamic scene size */ function GridBroadphase(options){ Broadphase.apply(this); options = Utils.defaults(options,{ xmin: -100, xmax: 100, ymin: -100, ymax: 100, nx: 10, ny: 10 }); this.xmin = options.xmin; this.ymin = options.ymin; this.xmax = options.xmax; this.ymax = options.ymax; this.nx = options.nx; this.ny = options.ny; this.binsizeX = (this.xmax-this.xmin) / this.nx; this.binsizeY = (this.ymax-this.ymin) / this.ny; } GridBroadphase.prototype = new Broadphase(); /** * Get collision pairs. * @method getCollisionPairs * @param {World} world * @return {Array} */ GridBroadphase.prototype.getCollisionPairs = function(world){ var result = [], bodies = world.bodies, Ncolliding = bodies.length, binsizeX = this.binsizeX, binsizeY = this.binsizeY, nx = this.nx, ny = this.ny, xmin = this.xmin, ymin = this.ymin, xmax = this.xmax, ymax = this.ymax; // Todo: make garbage free var bins=[], Nbins=nx*ny; for(var i=0; i= 0 && idx < Nbins){ bins[ idx ].push(bi); } } } } // Check each bin for(var i=0; i!==Nbins; i++){ var bin = bins[i]; for(var j=0, NbodiesInBin=bin.length; j!==NbodiesInBin; j++){ var bi = bin[j]; for(var k=0; k!==j; k++){ var bj = bin[k]; if(Broadphase.canCollide(bi,bj) && this.boundingVolumeCheck(bi,bj)){ result.push(bi,bj); } } } } return result; }; },{"../collision/Broadphase":9,"../math/vec2":30,"../shapes/Circle":35,"../shapes/Particle":39,"../shapes/Plane":40,"../utils/Utils":47}],11:[function(require,module,exports){ var Circle = require('../shapes/Circle'), Plane = require('../shapes/Plane'), Shape = require('../shapes/Shape'), Particle = require('../shapes/Particle'), Broadphase = require('../collision/Broadphase'), vec2 = require('../math/vec2'); module.exports = NaiveBroadphase; /** * Naive broadphase implementation. Does N^2 tests. * * @class NaiveBroadphase * @constructor * @extends Broadphase */ function NaiveBroadphase(){ Broadphase.call(this, Broadphase.NAIVE); } NaiveBroadphase.prototype = new Broadphase(); /** * Get the colliding pairs * @method getCollisionPairs * @param {World} world * @return {Array} */ NaiveBroadphase.prototype.getCollisionPairs = function(world){ var bodies = world.bodies, result = this.result; result.length = 0; for(var i=0, Ncolliding=bodies.length; i!==Ncolliding; i++){ var bi = bodies[i]; for(var j=0; j pos0 && pos < pos1){ // We got contact! if(justTest) return true; var c = this.createContactEquation(circleBody,lineBody,si,sj); vec2.scale(c.normalA, orthoDist, -1); vec2.normalize(c.normalA, c.normalA); vec2.scale( c.contactPointA, c.normalA, circleRadius); add(c.contactPointA, c.contactPointA, circleOffset); sub(c.contactPointA, c.contactPointA, circleBody.position); sub(c.contactPointB, projectedPoint, lineOffset); add(c.contactPointB, c.contactPointB, lineOffset); sub(c.contactPointB, c.contactPointB, lineBody.position); this.contactEquations.push(c); if(this.enableFriction){ this.frictionEquations.push(this.createFrictionFromContact(c)); } return 1; } } // Add corner // @todo reuse array object verts[0] = worldVertex0; verts[1] = worldVertex1; for(var i=0; i 0){ // Now project the circle onto the edge vec2.scale(orthoDist, worldTangent, d); sub(projectedPoint, circleOffset, orthoDist); // Check if the point is within the edge span var pos = dot(worldEdgeUnit, projectedPoint); var pos0 = dot(worldEdgeUnit, worldVertex0); var pos1 = dot(worldEdgeUnit, worldVertex1); if(pos > pos0 && pos < pos1){ // We got contact! if(justTest) return true; if(closestEdgeDistance === null || d*d 0){ for(var i=0; i= 0){ // Now project the particle onto the edge vec2.scale(orthoDist, worldTangent, d); sub(projectedPoint, particleOffset, orthoDist); // Check if the point is within the edge span var pos = dot(worldEdgeUnit, projectedPoint); var pos0 = dot(worldEdgeUnit, worldVertex0); var pos1 = dot(worldEdgeUnit, worldVertex1); if(pos > pos0 && pos < pos1){ // We got contact! if(justTest) return true; if(closestEdgeDistance === null || d*d r*r){ return 0; } if(justTest){ return true; } var c = this.createContactEquation(bodyA,bodyB,si,sj); sub(c.normalA, offsetB, offsetA); vec2.normalize(c.normalA,c.normalA); vec2.scale( c.contactPointA, c.normalA, radiusA); vec2.scale( c.contactPointB, c.normalA, -radiusB); add(c.contactPointA, c.contactPointA, offsetA); sub(c.contactPointA, c.contactPointA, bodyA.position); add(c.contactPointB, c.contactPointB, offsetB); sub(c.contactPointB, c.contactPointB, bodyB.position); this.contactEquations.push(c); if(this.enableFriction){ this.frictionEquations.push(this.createFrictionFromContact(c)); } return 1; }; /** * Plane/Convex Narrowphase * @method planeConvex * @param {Body} bi * @param {Plane} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Convex} sj * @param {Array} xj * @param {Number} aj */ Narrowphase.prototype[Shape.PLANE | Shape.CONVEX] = Narrowphase.prototype[Shape.PLANE | Shape.RECTANGLE] = Narrowphase.prototype.planeConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ var convexBody = bj, convexOffset = xj, convexShape = sj, convexAngle = aj, planeBody = bi, planeShape = si, planeOffset = xi, planeAngle = ai; var worldVertex = tmp1, worldNormal = tmp2, dist = tmp3; var numReported = 0; vec2.rotate(worldNormal, yAxis, planeAngle); for(var i=0; i 0) return 0; if(justTest) return true; var c = this.createContactEquation(planeBody,particleBody,sj,si); vec2.copy(c.normalA, worldNormal); vec2.scale( dist, c.normalA, 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.contactPointA, particleOffset, dist); sub( c.contactPointA, c.contactPointA, planeBody.position); // rj is from the body center to the particle center sub( c.contactPointB, particleOffset, particleBody.position ); this.contactEquations.push(c); if(this.enableFriction){ this.frictionEquations.push(this.createFrictionFromContact(c)); } return 1; }; /** * Circle/Particle Narrowphase * @method circleParticle * @param {Body} bi * @param {Circle} si * @param {Array} xi * @param {Number} ai * @param {Body} bj * @param {Particle} sj * @param {Array} xj * @param {Number} aj */ Narrowphase.prototype[Shape.CIRCLE | Shape.PARTICLE] = Narrowphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ var circleBody = bi, circleShape = si, circleOffset = xi, particleBody = bj, particleShape = sj, particleOffset = xj, dist = tmp1; sub(dist, particleOffset, circleOffset); if(vec2.squaredLength(dist) > circleShape.radius*circleShape.radius) return 0; if(justTest) return true; var c = this.createContactEquation(circleBody,particleBody,si,sj); vec2.copy(c.normalA, dist); vec2.normalize(c.normalA,c.normalA); // Vector from circle to contact point is the normal times the circle radius vec2.scale(c.contactPointA, c.normalA, circleShape.radius); add(c.contactPointA, c.contactPointA, circleOffset); sub(c.contactPointA, c.contactPointA, circleBody.position); // Vector from particle center to contact point is zero sub(c.contactPointB, particleOffset, particleBody.position); this.contactEquations.push(c); if(this.enableFriction){ this.frictionEquations.push(this.createFrictionFromContact(c)); } return 1; }; var capsulePlane_tmpCircle = new Circle(1), capsulePlane_tmp1 = vec2.create(), capsulePlane_tmp2 = vec2.create(), capsulePlane_tmp3 = vec2.create(); Narrowphase.prototype[Shape.PLANE | Shape.CAPSULE] = Narrowphase.prototype.planeCapsule = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ var end1 = capsulePlane_tmp1, end2 = capsulePlane_tmp2, circle = capsulePlane_tmpCircle, dst = capsulePlane_tmp3; // Compute world end positions vec2.set(end1, -sj.length/2, 0); vec2.rotate(end1,end1,aj); add(end1,end1,xj); vec2.set(end2, sj.length/2, 0); vec2.rotate(end2,end2,aj); add(end2,end2,xj); circle.radius = sj.radius; // Do Narrowphase as two circles var numContacts1 = this.circlePlane(bj,circle,end1,0, bi,si,xi,ai, justTest), numContacts2 = this.circlePlane(bj,circle,end2,0, bi,si,xi,ai, justTest); if(justTest) return numContacts1 || numContacts2; else return numContacts1 + numContacts2; }; /** * @method capsulePlane * @deprecated Use .planeCapsule() instead! */ Narrowphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ console.warn("Narrowphase.prototype.capsulePlane() is deprecated. Use .planeCapsule() instead!"); return this.planeCapsule( bj,sj,xj,aj, bi,si,xi,ai, justTest ); } /** * Creates ContactEquations and FrictionEquations for a collision. * @method circlePlane * @param {Body} bi The first body that should be connected to the equations. * @param {Circle} si The circle shape participating in the collision. * @param {Array} xi Extra offset to take into account for the Shape, in addition to the one in circleBody.position. Will *not* be rotated by circleBody.angle (maybe it should, for sake of homogenity?). Set to null if none. * @param {Body} bj The second body that should be connected to the equations. * @param {Plane} sj The Plane shape that is participating * @param {Array} xj Extra offset for the plane shape. * @param {Number} aj Extra angle to apply to the plane */ Narrowphase.prototype[Shape.CIRCLE | Shape.PLANE] = Narrowphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ var circleBody = bi, circleShape = si, circleOffset = xi, // Offset from body center, rotated! planeBody = bj, shapeB = sj, planeOffset = xj, planeAngle = aj; planeAngle = planeAngle || 0; // Vector from plane to circle var planeToCircle = tmp1, worldNormal = tmp2, temp = tmp3; sub(planeToCircle, circleOffset, planeOffset); // World plane normal vec2.rotate(worldNormal, yAxis, planeAngle); // Normal direction distance var d = dot(worldNormal, planeToCircle); if(d > circleShape.radius){ return 0; // No overlap. Abort. } if(justTest){ return true; } // Create contact var contact = this.createContactEquation(planeBody,circleBody,sj,si); // ni is the plane world normal vec2.copy(contact.normalA, worldNormal); // rj is the vector from circle center to the contact point vec2.scale(contact.contactPointB, contact.normalA, -circleShape.radius); add(contact.contactPointB, contact.contactPointB, circleOffset); sub(contact.contactPointB, contact.contactPointB, circleBody.position); // ri is the distance from plane center to contact. vec2.scale(temp, contact.normalA, d); sub(contact.contactPointA, planeToCircle, temp ); // Subtract normal distance vector from the distance vector add(contact.contactPointA, contact.contactPointA, planeOffset); sub(contact.contactPointA, contact.contactPointA, planeBody.position); this.contactEquations.push(contact); if(this.enableFriction){ this.frictionEquations.push( this.createFrictionFromContact(contact) ); } return 1; }; Narrowphase.convexPrecision = 1e-7; /** * 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[Shape.CONVEX | Shape.RECTANGLE] = Narrowphase.prototype[Shape.RECTANGLE] = Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest, precision ){ var sepAxis = tmp1, worldPoint = tmp2, worldPoint0 = tmp3, worldPoint1 = tmp4, worldEdge = tmp5, projected = tmp6, penetrationVec = tmp7, dist = tmp8, worldNormal = tmp9, numContacts = 0, precision = precision || Narrowphase.convexPrecision; var found = Narrowphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis); if(!found){ return 0; } // Make sure the separating axis is directed from shape i to shape j sub(dist,xj,xi); if(dot(sepAxis,dist) > 0){ vec2.scale(sepAxis,sepAxis,-1); } // Find edges with normals closest to the separating axis var closestEdge1 = Narrowphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis closestEdge2 = Narrowphase.getClosestEdge(sj,aj,sepAxis); if(closestEdge1 === -1 || closestEdge2 === -1){ return 0; } // Loop over the shapes for(var k=0; k<2; k++){ var closestEdgeA = closestEdge1, closestEdgeB = closestEdge2, shapeA = si, shapeB = sj, offsetA = xi, offsetB = xj, angleA = ai, angleB = aj, bodyA = bi, bodyB = bj; if(k === 0){ // Swap! var tmp; tmp = closestEdgeA; closestEdgeA = closestEdgeB; closestEdgeB = tmp; tmp = shapeA; shapeA = shapeB; shapeB = tmp; tmp = offsetA; offsetA = offsetB; offsetB = tmp; tmp = angleA; angleA = angleB; angleB = tmp; tmp = bodyA; bodyA = bodyB; bodyB = tmp; } // Loop over 2 points in convex B for(var j=closestEdgeB; j= 3){ if(justTest){ return true; } // worldPoint was on the "inside" side of each of the 3 checked edges. // Project it to the center edge and use the projection direction as normal // Create contact var c = this.createContactEquation(bodyA,bodyB,shapeA,shapeB); numContacts++; // Get center edge from body A var v0 = shapeA.vertices[(closestEdgeA) % shapeA.vertices.length], v1 = shapeA.vertices[(closestEdgeA+1) % shapeA.vertices.length]; // Construct the edge vec2.rotate(worldPoint0, v0, angleA); vec2.rotate(worldPoint1, v1, angleA); add(worldPoint0, worldPoint0, offsetA); add(worldPoint1, worldPoint1, offsetA); sub(worldEdge, worldPoint1, worldPoint0); vec2.rotate90cw(c.normalA, worldEdge); // Normal points out of convex A vec2.normalize(c.normalA,c.normalA); sub(dist, worldPoint, worldPoint0); // From edge point to the penetrating point var d = dot(c.normalA,dist); // Penetration vec2.scale(penetrationVec, c.normalA, d); // Vector penetration sub(c.contactPointA, worldPoint, offsetA); sub(c.contactPointA, c.contactPointA, penetrationVec); add(c.contactPointA, c.contactPointA, offsetA); sub(c.contactPointA, c.contactPointA, bodyA.position); sub(c.contactPointB, worldPoint, offsetB); add(c.contactPointB, c.contactPointB, offsetB); sub(c.contactPointB, c.contactPointB, bodyB.position); this.contactEquations.push(c); // Todo reduce to 1 friction equation if we have 2 contact points if(this.enableFriction) this.frictionEquations.push(this.createFrictionFromContact(c)); } } } return numContacts; }; // .projectConvex is called by other functions, need local tmp vectors var pcoa_tmp1 = vec2.fromValues(0,0); /** * Project a Convex onto a world-oriented axis * @method projectConvexOntoAxis * @static * @param {Convex} convexShape * @param {Array} convexOffset * @param {Number} convexAngle * @param {Array} worldAxis * @param {Array} result */ Narrowphase.projectConvexOntoAxis = function(convexShape, convexOffset, convexAngle, worldAxis, result){ var max=null, min=null, v, value, localAxis = pcoa_tmp1; // Convert the axis to local coords of the body vec2.rotate(localAxis, worldAxis, -convexAngle); // Get projected position of all vertices for(var i=0; i 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.rotate90cw(normal, edge); 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 <= Narrowphase.convexPrecision); 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; 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.rotate90cw(normal, edge); vec2.normalize(normal,normal); var d = dot(normal,localAxis); if(closestEdge == -1 || d > maxDot){ closestEdge = i % N; maxDot = d; } } return closestEdge; }; var circleHeightfield_candidate = vec2.create(), circleHeightfield_dist = vec2.create(), circleHeightfield_v0 = vec2.create(), circleHeightfield_v1 = vec2.create(), circleHeightfield_minCandidate = vec2.create(), circleHeightfield_worldNormal = vec2.create(), circleHeightfield_minCandidateNormal = vec2.create(); /** * @method circleHeightfield * @param {Body} bi * @param {Circle} si * @param {Array} xi * @param {Body} bj * @param {Heightfield} sj * @param {Array} xj * @param {Number} aj */ Narrowphase.prototype[Shape.CIRCLE | Shape.HEIGHTFIELD] = Narrowphase.prototype.circleHeightfield = function( circleBody,circleShape,circlePos,circleAngle, hfBody,hfShape,hfPos,hfAngle, justTest, radius ){ var data = hfShape.data, radius = radius || circleShape.radius, w = hfShape.elementWidth, dist = circleHeightfield_dist, candidate = circleHeightfield_candidate, minCandidate = circleHeightfield_minCandidate, minCandidateNormal = circleHeightfield_minCandidateNormal, worldNormal = circleHeightfield_worldNormal, v0 = circleHeightfield_v0, v1 = circleHeightfield_v1; // Get the index of the points to test against var idxA = Math.floor( (circlePos[0] - radius - hfPos[0]) / w ), idxB = Math.ceil( (circlePos[0] + radius - hfPos[0]) / w ); /*if(idxB < 0 || idxA >= data.length) return justTest ? false : 0;*/ if(idxA < 0) idxA = 0; if(idxB >= data.length) idxB = data.length-1; // Get max and min var max = data[idxA], min = data[idxB]; for(var i=idxA; i max) max = data[i]; } if(circlePos[1]-radius > max) return justTest ? false : 0; if(circlePos[1]+radius < min){ // Below the minimum point... We can just guess. // TODO } // 1. Check so center of circle is not inside the field. If it is, this wont work... // 2. For each edge // 2. 1. Get point on circle that is closest to the edge (scale normal with -radius) // 2. 2. Check if point is inside. var found = false; // Check all edges first for(var i=idxA; i= v0[0] && candidate[0] < v1[0] && d <= 0){ if(justTest){ return true; } found = true; // Store the candidate point, projected to the edge vec2.scale(dist,worldNormal,-d); vec2.add(minCandidate,candidate,dist); vec2.copy(minCandidateNormal,worldNormal); var c = this.createContactEquation(hfBody,circleBody,hfShape,circleShape); // Normal is out of the heightfield vec2.copy(c.normalA, minCandidateNormal); // Vector from circle to heightfield vec2.scale(c.contactPointB, c.normalA, -radius); add(c.contactPointB, c.contactPointB, circlePos); sub(c.contactPointB, c.contactPointB, circleBody.position); vec2.copy(c.contactPointA, minCandidate); vec2.sub(c.contactPointA, c.contactPointA, hfBody.position); this.contactEquations.push(c); if(this.enableFriction){ this.frictionEquations.push( this.createFrictionFromContact(c) ); } } } // Check all vertices found = false; if(radius > 0){ for(var i=idxA; i<=idxB; i++){ // Get point vec2.set(v0, i*w, data[i]); vec2.add(v0,v0,hfPos); vec2.sub(dist, circlePos, v0); if(vec2.squaredLength(dist) < radius*radius){ if(justTest) return true; found = true; var c = this.createContactEquation(hfBody,circleBody,hfShape,circleShape); // Construct normal - out of heightfield vec2.copy(c.normalA, dist); vec2.normalize(c.normalA,c.normalA); vec2.scale(c.contactPointB, c.normalA, -radius); add(c.contactPointB, c.contactPointB, circlePos); sub(c.contactPointB, c.contactPointB, circleBody.position); sub(c.contactPointA, v0, hfPos); add(c.contactPointA, c.contactPointA, hfPos); sub(c.contactPointA, c.contactPointA, hfBody.position); this.contactEquations.push(c); if(this.enableFriction){ this.frictionEquations.push(this.createFrictionFromContact(c)); } } } } if(found){ return 1; } return 0; }; var convexHeightfield_v0 = vec2.create(), convexHeightfield_v1 = vec2.create(), convexHeightfield_tilePos = vec2.create(), convexHeightfield_tempConvexShape = new Convex([vec2.create(),vec2.create(),vec2.create(),vec2.create()]); /** * @method circleHeightfield * @param {Body} bi * @param {Circle} si * @param {Array} xi * @param {Body} bj * @param {Heightfield} sj * @param {Array} xj * @param {Number} aj */ Narrowphase.prototype[Shape.RECTANGLE | Shape.HEIGHTFIELD] = Narrowphase.prototype[Shape.CONVEX | Shape.HEIGHTFIELD] = Narrowphase.prototype.convexHeightfield = function( convexBody,convexShape,convexPos,convexAngle, hfBody,hfShape,hfPos,hfAngle, justTest ){ var data = hfShape.data, w = hfShape.elementWidth, v0 = convexHeightfield_v0, v1 = convexHeightfield_v1, tilePos = convexHeightfield_tilePos, tileConvex = convexHeightfield_tempConvexShape; // Get the index of the points to test against var idxA = Math.floor( (convexBody.aabb.lowerBound[0] - hfPos[0]) / w ), idxB = Math.ceil( (convexBody.aabb.upperBound[0] - hfPos[0]) / w ); if(idxA < 0) idxA = 0; if(idxB >= data.length) idxB = data.length-1; // Get max and min var max = data[idxA], min = data[idxB]; for(var i=idxA; i max) max = data[i]; } if(convexBody.aabb.lowerBound[1] > max){ return justTest ? false : 0; } var found = false; var numContacts = 0; // Loop over all edges for(var i=idxA; i=0;j--) { if(a[j].aabb.lowerBound[axisIndex] <= v.aabb.lowerBound[axisIndex]){ break; } a[j+1] = a[j]; } a[j+1] = v; } return a; }; /** * Get the colliding pairs * @method getCollisionPairs * @param {World} world * @return {Array} */ SAPBroadphase.prototype.getCollisionPairs = function(world){ var bodies = this.axisList, result = this.result, axisIndex = this.axisIndex; result.length = 0; // Update all AABBs if needed var l = bodies.length; while(l--){ var b = bodies[l]; if(b.aabbNeedsUpdate){ b.updateAABB(); } } // Sort the lists SAPBroadphase.sortAxisList(bodies, axisIndex); // Look through the X list for(var i=0, N=bodies.length|0; i!==N; i++){ var bi = bodies[i]; for(var j=i+1; j G = [-n -rixn n rjxn] var r = vec2.create(); var ri = vec2.create(); // worldAnchorA var rj = vec2.create(); // worldAnchorB var that = this; normal.computeGq = function(){ var bodyA = this.bodyA, bodyB = this.bodyB, xi = bodyA.position, xj = bodyB.position; // Transform local anchors to world vec2.rotate(ri, localAnchorA, bodyA.angle); vec2.rotate(rj, localAnchorB, bodyB.angle); vec2.add(r, xi, rj); vec2.sub(r, r, ri); vec2.sub(r, r, xi); vec2.sub(r, bodyB.position, bodyA.position); return vec2.length(r) - that.distance; }; // Make the contact constraint bilateral this.setMaxForce(maxForce); } DistanceConstraint.prototype = new Constraint(); /** * Update the constraint equations. Should be done if any of the bodies changed position, before solving. * @method update */ var n = vec2.create(); var ri = vec2.create(); // worldAnchorA var rj = vec2.create(); // worldAnchorB DistanceConstraint.prototype.update = function(){ var normal = this.equations[0], bodyA = this.bodyA, bodyB = this.bodyB, distance = this.distance, xi = bodyA.position, xj = bodyB.position, G = normal.G; // Transform local anchors to world vec2.rotate(ri, this.localAnchorA, bodyA.angle); vec2.rotate(rj, this.localAnchorB, bodyB.angle); // 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; */ vec2.sub(n, bodyB.position, bodyA.position); vec2.normalize(n,n); G[0] = -n[0]; G[1] = -n[1]; G[3] = n[0]; G[4] = n[1]; }; /** * Set the max force to be used * @method setMaxForce * @param {Number} f */ DistanceConstraint.prototype.setMaxForce = function(f){ var normal = this.equations[0]; normal.minForce = -f; normal.maxForce = f; }; /** * Get the max force * @method getMaxForce * @return {Number} */ DistanceConstraint.prototype.getMaxForce = function(f){ var normal = this.equations[0]; return normal.maxForce; }; },{"../equations/Equation":22,"../math/vec2":30,"./Constraint":14}],16:[function(require,module,exports){ var Constraint = require('./Constraint') , Equation = require('../equations/Equation') , AngleLockEquation = require('../equations/AngleLockEquation') , vec2 = require('../math/vec2') module.exports = GearConstraint; /** * Connects two bodies at given offset points, letting them rotate relative to each other around this point. * @class GearConstraint * @constructor * @author schteppe * @param {Body} bodyA * @param {Body} bodyB * @param {Object} [options] * @param {Number} [options.angle=0] Relative angle between the bodies. * @param {Number} [options.ratio=1] Gear ratio. * @param {Number} [options.maxTorque] Maximum torque to apply. * @extends Constraint * @todo Ability to specify world points */ function GearConstraint(bodyA, bodyB, options){ options = options || {}; Constraint.call(this, bodyA, bodyB, Constraint.GEAR, options); this.equations = [ new AngleLockEquation(bodyA,bodyB,options), ]; /** * The relative angle * @property angle * @type {Number} */ this.angle = typeof(options.angle) === "number" ? options.angle : 0; /** * The gear ratio. * @property ratio * @type {Number} */ this.ratio = typeof(options.ratio) === "number" ? options.ratio : 1; // Set max torque if(typeof(options.maxTorque) === "number"){ this.setMaxTorque(options.maxTorque); } } GearConstraint.prototype = new Constraint(); GearConstraint.prototype.update = function(){ var eq = this.equations[0]; if(eq.ratio !== this.ratio){ eq.setRatio(this.ratio); } eq.angle = this.angle; }; /** * Set the max torque for the constraint. * @method setMaxTorque * @param {Number} torque */ GearConstraint.prototype.setMaxTorque = function(torque){ this.equations[0].setMaxTorque(torque); }; /** * Get the max torque for the constraint. * @method getMaxTorque * @return {Number} */ GearConstraint.prototype.getMaxTorque = function(torque){ return this.equations[0].maxForce; }; },{"../equations/AngleLockEquation":20,"../equations/Equation":22,"../math/vec2":30,"./Constraint":14}],17:[function(require,module,exports){ var Constraint = require('./Constraint') , vec2 = require('../math/vec2') , Equation = require('../equations/Equation') module.exports = LockConstraint; /** * Locks the relative position between two bodies. * * @class LockConstraint * @constructor * @author schteppe * @param {Body} bodyA * @param {Body} bodyB * @param {Object} [options] * @param {Array} [options.localOffsetB] The offset of bodyB in bodyA's frame. Default is the zero vector. * @param {number} [options.localAngleB=0] The angle of bodyB in bodyA's frame. * @param {number} [options.maxForce] * @extends Constraint */ function LockConstraint(bodyA, bodyB, options){ options = options || {}; Constraint.call(this,bodyA,bodyB,Constraint.LOCK,options); var maxForce = ( typeof(options.maxForce)==="undefined" ? Number.MAX_VALUE : options.maxForce ); var localOffsetB = options.localOffsetB || vec2.fromValues(0,0); localOffsetB = vec2.fromValues(localOffsetB[0],localOffsetB[1]); var localAngleB = options.localAngleB || 0; // Use 3 equations: // gx = (xj - xi - l) * xhat = 0 // gy = (xj - xi - l) * yhat = 0 // gr = (xi - xj + r) * that = 0 // // ...where: // l is the localOffsetB vector rotated to world in bodyA frame // r is the same vector but reversed and rotated from bodyB frame // xhat, yhat are world axis vectors // that is the tangent of r // // For the first two constraints, we get // G*W = (vj - vi - ldot ) * xhat // = (vj - vi - wi x l) * xhat // // Since (wi x l) * xhat = (l x xhat) * wi, we get // G*W = [ -1 0 (-l x xhat) 1 0 0] * [vi wi vj wj] // // The last constraint gives // GW = (vi - vj + wj x r) * that // = [ that 0 -that (r x t) ] var x = new Equation(bodyA,bodyB,-maxForce,maxForce), y = new Equation(bodyA,bodyB,-maxForce,maxForce), rot = new Equation(bodyA,bodyB,-maxForce,maxForce); var l = vec2.create(), g = vec2.create(), that = this; x.computeGq = function(){ vec2.rotate(l, that.localOffsetB, bodyA.angle); vec2.sub(g, bodyB.position, bodyA.position); vec2.sub(g, g, l); return g[0]; }; y.computeGq = function(){ vec2.rotate(l, that.localOffsetB, bodyA.angle); vec2.sub(g, bodyB.position, bodyA.position); vec2.sub(g, g, l); return g[1]; }; var r = vec2.create(), t = vec2.create(); rot.computeGq = function(){ vec2.rotate(r, that.localOffsetB, bodyB.angle - that.localAngleB); vec2.scale(r,r,-1); vec2.sub(g,bodyA.position,bodyB.position); vec2.add(g,g,r); vec2.rotate(t,r,-Math.PI/2); vec2.normalize(t,t); return vec2.dot(g,t); }; /** * The offset of bodyB in bodyA's frame. * @property {Array} localOffsetB */ this.localOffsetB = localOffsetB; /** * The offset angle of bodyB in bodyA's frame. * @property {Number} localAngleB */ this.localAngleB = localAngleB; this.equations.push(x, y, rot); this.setMaxForce(maxForce); } LockConstraint.prototype = new Constraint(); /** * Set the maximum force to be applied. * @method setMaxForce * @param {Number} force */ LockConstraint.prototype.setMaxForce = function(force){ var eqs = this.equations; 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. * @param {Number} [options.upperLimit] * @param {Number} [options.lowerLimit] */ function PrismaticConstraint(bodyA, bodyB, options){ options = options || {}; Constraint.call(this,bodyA,bodyB,Constraint.PRISMATIC,options); // 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.updateJacobian = 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 = typeof(options.lowerLimit)!=="undefined" ? true : false; /** * Set to true to enable upper limit. * @property upperLimitEnabled * @type {Boolean} */ this.upperLimitEnabled = typeof(options.upperLimit)!=="undefined" ? true : false; /** * Lower constraint limit. The constraint position is forced to be larger than this value. * @property lowerLimit * @type {Number} */ this.lowerLimit = typeof(options.lowerLimit)!=="undefined" ? options.lowerLimit : 0; /** * Upper constraint limit. The constraint position is forced to be smaller than this value. * @property upperLimit * @type {Number} */ this.upperLimit = typeof(options.upperLimit)!=="undefined" ? options.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.bodyA, bj = this.bodyB, 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.updateJacobian(); // 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.contactPointA or .contactPointB 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.normalA, worldAxisA, -1); vec2.sub(upperLimitEquation.contactPointA, worldAnchorA, bodyA.position); vec2.sub(upperLimitEquation.contactPointB, worldAnchorB, bodyB.position); vec2.scale(tmp,worldAxisA,upperLimit); vec2.add(upperLimitEquation.contactPointA,upperLimitEquation.contactPointA,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.normalA, worldAxisA, 1); vec2.sub(lowerLimitEquation.contactPointA, worldAnchorA, bodyA.position); vec2.sub(lowerLimitEquation.contactPointB, worldAnchorB, bodyB.position); vec2.scale(tmp,worldAxisA,lowerLimit); vec2.sub(lowerLimitEquation.contactPointB,lowerLimitEquation.contactPointB,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":21,"../equations/Equation":22,"../equations/RotationalLockEquation":24,"../math/vec2":30,"./Constraint":14}],19:[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 {Array} 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 {Array} pivotB See pivotA. * @param {Object} [options] * @param {Number} [options.maxForce] The maximum force that should be applied to constrain the bodies. * @extends Constraint * @todo Ability to specify world points * @todo put pivot parameters in the options object? */ function RevoluteConstraint(bodyA, pivotA, bodyB, pivotB, options){ options = options || {}; Constraint.call(this,bodyA,bodyB,Constraint.REVOLUTE,options); var maxForce = this.maxForce = typeof(options.maxForce) !== "undefined" ? options.maxForce : Number.MAX_VALUE; /** * @property {Array} pivotA */ this.pivotA = vec2.fromValues(pivotA[0],pivotA[1]); /** * @property {Array} pivotB */ this.pivotB = vec2.fromValues(pivotB[0],pivotB[1]); // 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]; var that = this; x.computeGq = function(){ vec2.rotate(worldPivotA, that.pivotA, bodyA.angle); vec2.rotate(worldPivotB, that.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, that.pivotA, bodyA.angle); vec2.rotate(worldPivotB, that.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); /** * Indicates whether the motor is enabled. Use .enableMotor() to enable the constraint motor. * @property {Boolean} motorEnabled * @readOnly */ this.motorEnabled = false; /** * The constraint position. * @property angle * @type {Number} * @readOnly */ 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":22,"../equations/RotationalLockEquation":24,"../equations/RotationalVelocityEquation":25,"../math/vec2":30,"./Constraint":14}],20:[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} bodyA * @param {Body} bodyB * @param {Object} [options] * @param {Number} [options.angle] Angle to add to the local vector in body A. * @param {Number} [options.ratio] Gear ratio */ function AngleLockEquation(bodyA, bodyB, options){ options = options || {}; Equation.call(this,bodyA,bodyB,-Number.MAX_VALUE,Number.MAX_VALUE); this.angle = options.angle || 0; /** * The gear ratio. * @property {Number} ratio * @private * @see setRatio */ 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.bodyA.angle - this.bodyB.angle + this.angle; }; /** * Set the gear ratio for this equation * @method setRatio * @param {Number} ratio */ AngleLockEquation.prototype.setRatio = function(ratio){ var G = this.G; G[2] = ratio; G[5] = -1; this.ratio = ratio; }; /** * Set the max force for the equation. * @method setMaxTorque * @param {Number} torque */ AngleLockEquation.prototype.setMaxTorque = function(torque){ this.maxForce = torque; this.minForce = -torque; }; },{"../math/vec2":30,"./Equation":22}],21:[function(require,module,exports){ var Equation = require("./Equation"), vec2 = require('../math/vec2'); 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} bodyA * @param {Body} bodyB */ function ContactEquation(bodyA, bodyB){ Equation.call(this, bodyA, bodyB, 0, Number.MAX_VALUE); /** * Vector from body i center of mass to the contact point. * @property contactPointA * @type {Array} */ this.contactPointA = vec2.create(); this.penetrationVec = vec2.create(); /** * World-oriented vector from body A center of mass to the contact point. * @property contactPointB * @type {Array} */ this.contactPointB = vec2.create(); /** * The normal vector, pointing out of body i * @property normalA * @type {Array} */ this.normalA = vec2.create(); /** * The restitution to use (0=no bounciness, 1=max bounciness). * @property restitution * @type {Number} */ this.restitution = 0; /** * This property is set to true if this is the first impact between the bodies (not persistant contact). * @property firstImpact * @type {Boolean} * @readOnly */ 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.bodyA, bj = this.bodyB, ri = this.contactPointA, rj = this.contactPointB, xi = bi.position, xj = bj.position; var penetrationVec = this.penetrationVec, n = this.normalA, 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 { Gq = vec2.dot(n,penetrationVec); GW = this.computeGW(); } var GiMf = this.computeGiMf(); var B = - Gq * a - GW * b - h*GiMf; return B; }; },{"../math/vec2":30,"./Equation":22}],22:[function(require,module,exports){ module.exports = Equation; var vec2 = require('../math/vec2'), Utils = require('../utils/Utils'), Body = require('../objects/Body'); /** * Base class for constraint equations. * @class Equation * @constructor * @param {Body} bodyA First body participating in the equation * @param {Body} bodyB Second body participating in the equation * @param {number} minForce Minimum force to apply. Default: -Number.MAX_VALUE * @param {number} maxForce Maximum force to apply. Default: Number.MAX_VALUE */ function Equation(bodyA, bodyB, minForce, maxForce){ /** * Minimum force to apply when solving. * @property minForce * @type {Number} */ this.minForce = typeof(minForce)==="undefined" ? -Number.MAX_VALUE : minForce; /** * Max force to apply when solving. * @property maxForce * @type {Number} */ this.maxForce = typeof(maxForce)==="undefined" ? Number.MAX_VALUE : maxForce; /** * First body participating in the constraint * @property bodyA * @type {Body} */ this.bodyA = bodyA; /** * Second body participating in the constraint * @property bodyB * @type {Body} */ this.bodyB = bodyB; /** * 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 = Equation.DEFAULT_STIFFNESS; /** * The number of time steps needed to stabilize the constraint equation. Typically between 3 and 5 time steps. * @property relaxation * @type {Number} */ this.relaxation = Equation.DEFAULT_RELAXATION; /** * 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); for(var i=0; i<6; i++){ this.G[i]=0; } // 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.epsilon = 0; this.timeStep = 1/60; /** * Indicates if stiffness or relaxation was changed. * @property {Boolean} needsUpdate */ this.needsUpdate = true; /** * 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; /** * Relative velocity. * @property {Number} relativeVelocity */ this.relativeVelocity = 0; /** * Whether this equation is enabled or not. If true, it will be added to the solver. * @property {Boolean} enabled */ this.enabled = true; } Equation.prototype.constructor = Equation; /** * The default stiffness when creating a new Equation. * @static * @property {Number} DEFAULT_STIFFNESS * @default 1e6 */ Equation.DEFAULT_STIFFNESS = 1e6; /** * The default relaxation when creating a new Equation. * @static * @property {Number} DEFAULT_RELAXATION * @default 4 */ Equation.DEFAULT_RELAXATION = 4; /** * Compute SPOOK parameters .a, .b and .epsilon according to the current parameters. See equations 9, 10 and 11 in the SPOOK notes. * @method update */ Equation.prototype.update = function(){ var k = this.stiffness, d = this.relaxation, h = this.timeStep; this.a = 4.0 / (h * (1 + 4 * d)); this.b = (4.0 * d) / (1 + 4 * d); this.epsilon = 4.0 / (h * h * k * (1 + 4 * d)); this.needsUpdate = false; }; 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.bodyA, bj = this.bodyB, xi = bi.position, xj = bj.position, ai = bi.angle, aj = bj.angle; return Gmult(G, qi, ai, qj, aj) + this.offset; }; /** * Computes G*W, where W are the body velocities * @method computeGW * @return {Number} */ Equation.prototype.computeGW = function(){ var G = this.G, bi = this.bodyA, bj = this.bodyB, vi = bi.velocity, vj = bj.velocity, wi = bi.angularVelocity, wj = bj.angularVelocity; return Gmult(G,vi,wi,vj,wj) + this.relativeVelocity; }; /** * Computes G*Wlambda, where W are the body velocities * @method computeGWlambda * @return {Number} */ Equation.prototype.computeGWlambda = function(){ var G = this.G, bi = this.bodyA, bj = this.bodyB, 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.bodyA, bj = this.bodyB, fi = bi.force, ti = bi.angularForce, fj = bj.force, tj = bj.angularForce, invMassi = getBodyInvMass(bi), invMassj = getBodyInvMass(bj), invIi = getBodyInvInertia(bi), invIj = getBodyInvInertia(bj), G = this.G; vec2.scale(iMfi, fi,invMassi); vec2.scale(iMfj, fj,invMassj); return Gmult(G,iMfi,ti*invIi,iMfj,tj*invIj); }; function getBodyInvMass(body){ if(body.sleepState === Body.SLEEPING){ return 0; } else { return body.invMass; } } function getBodyInvInertia(body){ if(body.sleepState === Body.SLEEPING){ return 0; } else { return body.invInertia; } } /** * Computes G*inv(M)*G' * @method computeGiMGt * @return {Number} */ Equation.prototype.computeGiMGt = function(){ var bi = this.bodyA, bj = this.bodyB, invMassi = getBodyInvMass(bi), invMassj = getBodyInvMass(bj), invIi = getBodyInvInertia(bi), invIj = getBodyInvInertia(bj), 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(); /** * Add constraint velocity to the bodies. * @method addToWlambda * @param {Number} deltalambda */ Equation.prototype.addToWlambda = function(deltalambda){ var bi = this.bodyA, bj = this.bodyB, temp = addToWlambda_temp, Gi = addToWlambda_Gi, Gj = addToWlambda_Gj, ri = addToWlambda_ri, rj = addToWlambda_rj, invMassi = getBodyInvMass(bi), invMassj = getBodyInvMass(bj), invIi = getBodyInvInertia(bi), invIj = getBodyInvInertia(bj), Mdiag = addToWlambda_Mdiag, G = this.G; Gi[0] = G[0]; Gi[1] = G[1]; Gj[0] = G[3]; Gj[1] = G[4]; // Add to linear velocity // v_lambda += inv(M) * delta_lamba * G vec2.scale(temp, Gi, invMassi*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); bi.wlambda += invIi * G[2] * deltalambda; vec2.scale(temp, Gj, invMassj*deltalambda); vec2.add( bj.vlambda, bj.vlambda, temp); //bj.wlambda -= vec2.crossLength(temp,rj); bj.wlambda += invIj * G[5] * deltalambda; }; /** * 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/vec2":30,"../objects/Body":31,"../utils/Utils":47}],23:[function(require,module,exports){ var 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} bodyA * @param {Body} bodyB * @param {Number} slipForce * @extends Equation */ function FrictionEquation(bodyA, bodyB, slipForce){ Equation.call(this, bodyA, bodyB, -slipForce, slipForce); /** * Relative vector from center of body A to the contact point, world oriented. * @property contactPointA * @type {Array} */ this.contactPointA = vec2.create(); /** * Relative vector from center of body B to the contact point, world oriented. * @property contactPointB * @type {Array} */ this.contactPointB = vec2.create(); /** * Tangent vector that the friction force will act along. World oriented. * @property t * @type {Array} */ 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 */ FrictionEquation.prototype.setSlipForce = function(slipForce){ this.maxForce = slipForce; this.minForce = -slipForce; }; /** * Get the max force for the constraint. * @method getSlipForce * @return {Number} */ FrictionEquation.prototype.getSlipForce = function(){ return this.maxForce; }; FrictionEquation.prototype.computeB = function(a,b,h){ var bi = this.bodyA, bj = this.bodyB, ri = this.contactPointA, rj = this.contactPointB, 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(), GiMf = this.computeGiMf(); var B = /* - g * a */ - GW * b - h*GiMf; return B; }; },{"../math/vec2":30,"../utils/Utils":47,"./Equation":22}],24:[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} bodyA * @param {Body} bodyB * @param {Object} [options] * @param {Number} [options.angle] Angle to add to the local vector in body i. */ function RotationalLockEquation(bodyA, bodyB, options){ options = options || {}; Equation.call(this, bodyA, bodyB, -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.bodyA.angle+this.angle); vec2.rotate(worldVectorB,yAxis,this.bodyB.angle); return vec2.dot(worldVectorA,worldVectorB); }; },{"../math/vec2":30,"./Equation":22}],25:[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} bodyA * @param {Body} bodyB */ function RotationalVelocityEquation(bodyA, bodyB){ Equation.call(this, bodyA, bodyB, -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(); var B = - GW * b - h*GiMf; return B; }; },{"../math/vec2":30,"./Equation":22}],26:[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, context ) { listener.context = context || this; 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(listener){ if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) { return true; } } else { if ( listeners[ type ] !== undefined ) { 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 ++ ) { var listener = listenerArray[ i ]; listener.call( listener.context, event ); } } return this; } }; },{}],27:[function(require,module,exports){ var Material = require('./Material'); var Equation = require('../equations/Equation'); module.exports = ContactMaterial; /** * Defines what happens when two materials meet, such as what friction coefficient to use. You can also set other things such as restitution, surface velocity and constraint parameters. * @class ContactMaterial * @constructor * @param {Material} materialA * @param {Material} materialB * @param {Object} [options] * @param {Number} [options.friction=0.3] Friction coefficient. * @param {Number} [options.restitution=0] Restitution coefficient aka "bounciness". * @param {Number} [options.stiffness] ContactEquation stiffness. * @param {Number} [options.relaxation] ContactEquation relaxation. * @param {Number} [options.frictionStiffness] FrictionEquation stiffness. * @param {Number} [options.frictionRelaxation] FrictionEquation relaxation. * @param {Number} [options.surfaceVelocity=0] Surface velocity. * @author schteppe */ function ContactMaterial(materialA, materialB, options){ options = options || {}; if(!(materialA instanceof Material) || !(materialB instanceof Material)) throw new Error("First two arguments must be Material instances."); /** * The contact material identifier * @property id * @type {Number} */ this.id = ContactMaterial.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) : Equation.DEFAULT_STIFFNESS; /** * Relaxation of the resulting ContactEquation that this ContactMaterial generate * @property relaxation * @type {Number} */ this.relaxation = typeof(options.relaxation) !== "undefined" ? Number(options.relaxation) : Equation.DEFAULT_RELAXATION; /** * Stiffness of the resulting FrictionEquation that this ContactMaterial generate * @property frictionStiffness * @type {Number} */ this.frictionStiffness = typeof(options.frictionStiffness) !== "undefined" ? Number(options.frictionStiffness) : Equation.DEFAULT_STIFFNESS; /** * Relaxation of the resulting FrictionEquation that this ContactMaterial generate * @property frictionRelaxation * @type {Number} */ this.frictionRelaxation = typeof(options.frictionRelaxation) !== "undefined" ? Number(options.frictionRelaxation) : Equation.DEFAULT_RELAXATION; /** * Will add surface velocity to this material. If bodyA rests on top if bodyB, and the surface velocity is positive, bodyA will slide to the right. * @property {Number} surfaceVelocity */ this.surfaceVelocity = typeof(options.surfaceVelocity) !== "undefined" ? Number(options.surfaceVelocity) : 0; } ContactMaterial.idCounter = 0; },{"../equations/Equation":22,"./Material":28}],28:[function(require,module,exports){ module.exports = Material; /** * Defines a physics material. * @class Material * @constructor * @param string name * @author schteppe */ function Material(){ /** * The material identifier * @property id * @type {Number} */ this.id = Material.idCounter++; }; Material.idCounter = 0; },{}],29:[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; },{}],30:[function(require,module,exports){ /** * The vec2 object from glMatrix, with some extensions and some removed methods. See http://glmatrix.net for full doc. * @class vec2 */ var vec2 = require('../../build/vec2').vec2; /** * 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; }; /** * Rotate a vector 90 degrees clockwise * @method rotate90cw * @static * @param {Float32Array} out * @param {Float32Array} a * @param {Number} angle */ vec2.rotate90cw = function(out, a) { out[0] = a[1]; out[1] = -a[0]; }; /** * Transform a point position to local frame. * @method toLocalFrame * @param {Array} out * @param {Array} worldPoint * @param {Array} framePosition * @param {Number} frameAngle */ vec2.toLocalFrame = function(out, worldPoint, framePosition, frameAngle){ vec2.copy(out, worldPoint); vec2.sub(out, out, framePosition); vec2.rotate(out, out, -frameAngle); }; /** * Transform a point position to global frame. * @method toGlobalFrame * @param {Array} out * @param {Array} localPoint * @param {Array} framePosition * @param {Number} 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; },{"../../build/vec2":1}],31:[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 * @extends EventEmitter * @param {Object} [options] * @param {Number} [options.mass=0] A number >= 0. If zero, the .motionState will be set to Body.STATIC. * @param {Array} [options.position] * @param {Array} [options.velocity] * @param {Number} [options.angle=0] * @param {Number} [options.angularVelocity=0] * @param {Array} [options.force] * @param {Number} [options.angularForce=0] * @param {Number} [options.fixedRotation=false] */ 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 Array. * @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; /** * 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 interpolated angle of the body. * @property interpolatedAngle * @type {Number} */ this.interpolatedAngle = 0; /** * The previous position of the body. * @property previousPosition * @type {Array} */ this.previousPosition = vec2.fromValues(0,0); /** * The previous angle of the body. * @property previousAngle * @type {Number} */ this.previousAngle = 0; /** * The velocity of the body * @property velocity * @type {Array} */ 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 {Array} */ this.vlambda = vec2.fromValues(0,0); /** * Angular constraint velocity that was added to the body during last step. * @property wlambda * @type {Array} */ this.wlambda = 0; /** * The angle of the body, in radians. * @property angle * @type {number} * @example * // The angle property is not normalized to the interval 0 to 2*pi, it can be any value. * // If you need a value between 0 and 2*pi, use the following function to normalize it. * function normalizeAngle(angle){ * angle = angle % (2*Math.PI); * if(angle < 0){ * angle += (2*Math.PI); * } * return angle; * } */ this.angle = options.angle || 0; /** * The angular velocity of the body, in radians per second. * @property angularVelocity * @type {number} */ this.angularVelocity = options.angularVelocity || 0; /** * The force acting on the body. Since the body force (and {{#crossLink "Body/angularForce:property"}}{{/crossLink}}) will be zeroed after each step, so you need to set the force before each step. * @property force * @type {Array} * * @example * // This produces a forcefield of 1 Newton in the positive x direction. * 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 {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(){ if(this.motionState === Body.STATIC || this.motionState === Body.KINEMATIC){ this.mass = Number.MAX_VALUE; this.invMass = 0; this.inertia = Number.MAX_VALUE; this.invInertia = 0; } else { var shapes = this.shapes, N = shapes.length, m = this.mass / N, I = 0; if(!this.fixedRotation){ for(var i=0; i0 ? 1/I : 0; } else { this.inertia = Number.MAX_VALUE; this.invInertia = 0; } // Inverse mass properties are easy this.invMass = 1/this.mass;// > 0 ? 1/this.mass : 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 {Array} force The force to add. * @param {Array} 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 {Array} out The vector to store the result in * @param {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; } }; /** * Wake the body up. Normally you should not need this, as the body is automatically awoken at events such as collisions. * Sets the sleepState to {{#crossLink "Body/AWAKE:property"}}Body.AWAKE{{/crossLink}} and emits the wakeUp event if the body wasn't awake before. * @method wakeUp */ Body.prototype.wakeUp = function(){ var s = this.sleepState; this.sleepState = Body.AWAKE; this.idleTime = 0; if(s !== Body.AWAKE){ this.emit(Body.wakeUpEvent); } }; /** * Force body sleep * @method sleep */ Body.prototype.sleep = function(){ this.sleepState = Body.SLEEPING; this.angularVelocity = 0; this.angularForce = 0; vec2.set(this.velocity,0,0); vec2.set(this.force,0,0); this.emit(Body.sleepEvent); }; //var velo2 = vec2.create(); /** * @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, dontSleep, dt){ if(!this.allowSleep || this.motionState === Body.SLEEPING){ return; } this.wantsToSleep = false; var sleepState = this.sleepState, speedSquared = vec2.squaredLength(this.velocity) + Math.pow(this.angularVelocity,2), speedLimitSquared = Math.pow(this.sleepSpeedLimit,2); // Add to idle time if(speedSquared >= speedLimitSquared){ this.idleTime = 0; this.sleepState = Body.AWAKE; } else { this.idleTime += dt; this.sleepState = Body.SLEEPY; } if(this.idleTime > this.sleepTimeLimit){ if(!dontSleep){ this.sleep(); } else { this.wantsToSleep = true; } } /* 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.wantsToSleep = true; if(!dontSleep){ this.sleep(); } } */ }; Body.prototype.getVelocityFromPosition = function(store, timeStep){ store = store || vec2.create(); vec2.sub(store, this.position, this.previousPosition); vec2.scale(store, store, 1/timeStep); return store; }; Body.prototype.getAngularVelocityFromPosition = function(timeStep){ return (this.angle - this.previousAngle) / timeStep; }; /** * @event sleepy */ Body.sleepyEvent = { type: "sleepy" }; /** * @event sleep */ Body.sleepEvent = { type: "sleep" }; /** * @event wakeup */ 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":8,"../events/EventEmitter":26,"../math/vec2":30,"../shapes/Convex":36,"poly-decomp":6}],32:[function(require,module,exports){ var vec2 = require('../math/vec2'); var Utils = require('../utils/Utils'); module.exports = Spring; /** * A spring, connecting two bodies. The Spring explicitly adds force and angularForce to the bodies and does therefore not put load on the solver. * * @class Spring * @constructor * @param {Body} bodyA * @param {Body} bodyB * @param {Object} [options] * @param {number} [options.restLength=1] A number > 0. Default: 1 * @param {number} [options.stiffness=100] Spring constant (see Hookes Law). A number >= 0. * @param {number} [options.damping=1] A number >= 0. Default: 1 * @param {Array} [options.localAnchorA] Where to hook the spring to body A, in local body coordinates. Defaults to the body center. * @param {Array} [options.localAnchorB] * @param {Array} [options.worldAnchorA] Where to hook the spring to body A, in world coordinates. Overrides the option "localAnchorA" if given. * @param {Array} [options.worldAnchorB] */ function Spring(bodyA,bodyB,options){ options = Utils.defaults(options,{ restLength: 1, stiffness: 100, damping: 1, localAnchorA: [0,0], localAnchorB: [0,0], }); /** * Rest length of the spring. * @property restLength * @type {number} */ this.restLength = options.restLength; /** * Stiffness of the spring. * @property stiffness * @type {number} */ this.stiffness = options.stiffness; /** * Damping of the spring. * @property damping * @type {number} */ this.damping = options.damping; /** * 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.create(); vec2.copy(this.localAnchorA, options.localAnchorA); /** * Anchor for bodyB in local bodyB coordinates. * @property localAnchorB * @type {Array} */ this.localAnchorB = vec2.create(); 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":30,"../utils/Utils":47}],33:[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'), Heightfield : require('./shapes/Heightfield'), Line : require('./shapes/Line'), LockConstraint : require('./constraints/LockConstraint'), Material : require('./material/Material'), Narrowphase : require('./collision/Narrowphase'), 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'), SAPBroadphase : require('./collision/SAPBroadphase'), Shape : require('./shapes/Shape'), Solver : require('./solver/Solver'), Spring : require('./objects/Spring'), Utils : require('./utils/Utils'), World : require('./world/World'), vec2 : require('./math/vec2'), version : require('../package.json').version, }; },{"../package.json":7,"./collision/AABB":8,"./collision/Broadphase":9,"./collision/GridBroadphase":10,"./collision/NaiveBroadphase":11,"./collision/Narrowphase":12,"./collision/SAPBroadphase":13,"./constraints/Constraint":14,"./constraints/DistanceConstraint":15,"./constraints/GearConstraint":16,"./constraints/LockConstraint":17,"./constraints/PrismaticConstraint":18,"./constraints/RevoluteConstraint":19,"./equations/AngleLockEquation":20,"./equations/ContactEquation":21,"./equations/Equation":22,"./equations/FrictionEquation":23,"./equations/RotationalVelocityEquation":25,"./events/EventEmitter":26,"./material/ContactMaterial":27,"./material/Material":28,"./math/vec2":30,"./objects/Body":31,"./objects/Spring":32,"./shapes/Capsule":34,"./shapes/Circle":35,"./shapes/Convex":36,"./shapes/Heightfield":37,"./shapes/Line":38,"./shapes/Particle":39,"./shapes/Plane":40,"./shapes/Rectangle":41,"./shapes/Shape":42,"./solver/GSSolver":43,"./solver/Solver":44,"./utils/Utils":47,"./world/World":51}],34:[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){ /** * The distance between the end points. * @property {Number} length */ this.length = length || 1; /** * The radius of the capsule. * @property {Number} radius */ 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; }; /** * @method updateBoundingRadius */ Capsule.prototype.updateBoundingRadius = function(){ this.boundingRadius = this.radius + this.length/2; }; /** * @method updateArea */ 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":30,"./Shape":42}],35:[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=1] 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(); /** * @method computeMomentOfInertia * @param {Number} mass * @return {Number} */ Circle.prototype.computeMomentOfInertia = function(mass){ var r = this.radius; return mass * r * r / 2; }; /** * @method updateBoundingRadius * @return {Number} */ Circle.prototype.updateBoundingRadius = function(){ this.boundingRadius = this.radius; }; /** * @method updateArea * @return {Number} */ 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":30,"./Shape":42}],36:[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 = []; // Copy the verts for(var i=0; i r2) r2 = l2; } this.boundingRadius = Math.sqrt(r2); }; /** * Get the area of the triangle spanned by the three points a, b, c. The area is positive if the points are given in counter-clockwise order, otherwise negative. * @static * @method triangleArea * @param {Array} a * @param {Array} b * @param {Array} c * @return {Number} */ Convex.triangleArea = function(a,b,c){ return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1]))) * 0.5; } /** * 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) var m = Convex.triangleArea(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":29,"../math/vec2":30,"./Shape":42,"poly-decomp":6}],37:[function(require,module,exports){ var Shape = require('./Shape') , vec2 = require('../math/vec2') , Utils = require('../utils/Utils'); module.exports = Heightfield; /** * Heightfield shape class. Height data is given as an array. These data points are spread out evenly with a distance "elementWidth". * @class Heightfield * @extends Shape * @constructor * @param {Array} data * @param {Number} maxValue * @param {Number} elementWidth * @todo Should take maxValue as an option and also be able to compute it itself if not given. * @todo Should be possible to use along all axes, not just y */ function Heightfield(data, options){ options = Utils.defaults(options, { maxValue : null, minValue : null, elementWidth : 0.1 }); if(options.minValue === null || options.maxValue === null){ options.maxValue = data[0]; options.minValue = data[0]; for(var i=0; i !== data.length; i++){ var v = data[i]; if(v > options.maxValue){ options.maxValue = v; } if(v < options.minValue){ options.minValue = v; } } } /** * An array of numbers, or height values, that are spread out along the x axis. * @property {array} data */ this.data = data; /** * Max value of the data * @property {number} maxValue */ this.maxValue = options.maxValue; /** * Max value of the data * @property {number} minValue */ this.minValue = options.minValue; /** * The width of each element * @property {number} elementWidth */ this.elementWidth = options.elementWidth; Shape.call(this,Shape.HEIGHTFIELD); } Heightfield.prototype = new Shape(); /** * @method computeMomentOfInertia * @param {Number} mass * @return {Number} */ Heightfield.prototype.computeMomentOfInertia = function(mass){ return Number.MAX_VALUE; }; Heightfield.prototype.updateBoundingRadius = function(){ this.boundingRadius = Number.MAX_VALUE; }; Heightfield.prototype.updateArea = function(){ var data = this.data, area = 0; for(var i=0; ithis 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; /** * Set to true if you want this shape to be a sensor. A sensor does not generate contacts, but it still reports contact events. This is good if you want to know if a shape is overlapping another shape, without them generating contacts. * @property {Boolean} sensor */ this.sensor = false; this.updateArea(); }; Shape.idCounter = 0; /** * @static * @property {Number} CIRCLE */ Shape.CIRCLE = 1; /** * @static * @property {Number} PARTICLE */ Shape.PARTICLE = 2; /** * @static * @property {Number} PLANE */ Shape.PLANE = 4; /** * @static * @property {Number} CONVEX */ Shape.CONVEX = 8; /** * @static * @property {Number} LINE */ Shape.LINE = 16; /** * @static * @property {Number} RECTANGLE */ Shape.RECTANGLE = 32; /** * @static * @property {Number} CAPSULE */ Shape.CAPSULE = 64; /** * @static * @property {Number} HEIGHTFIELD */ Shape.HEIGHTFIELD = 128; /** * 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 }; },{}],43:[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=10] * @param {Number} [options.tolerance=0] */ function GSSolver(options){ Solver.call(this,options,Solver.GS); 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, per constraint. If the total error is below this limit, the solver will stop iterating. Set to zero for as good solution as possible, but to something larger than zero to make computations faster. * @property tolerance * @type {Number} */ this.tolerance = options.tolerance || 1e-10; 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); /** * 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 solver iterations that are done to approximate normal forces. When these iterations are done, friction force will be computed from the contact normal forces. These friction forces will override any other friction forces set from the World for example. * The solver will use less iterations if the solution is below the .tolerance. * @property frictionIterations * @type {Number} */ this.frictionIterations = 0; /** * The number of iterations that were made during the last solve. If .tolerance is zero, this value will always be equal to .iterations, but if .tolerance is larger than zero, and the solver can quit early, then this number will be somewhere between 1 and .iterations. * @property {Number} usedIterations */ this.usedIterations = 0; } GSSolver.prototype = new Solver(); function setArrayZero(array){ var l = array.length; while(l--){ array[l] = +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, maxFrictionIter = this.frictionIterations, equations = this.equations, Neq = equations.length, tolSquared = Math.pow(this.tolerance*Neq, 2), bodies = world.bodies, Nbodies = world.bodies.length, add = vec2.add, set = vec2.set, useZeroRHS = this.useZeroRHS, lambda = this.lambda; this.usedIterations = 0; // 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); } setArrayZero(lambda); var invCs = this.invCs, Bs = this.Bs, lambda = this.lambda; for(var i=0; i!==equations.length; i++){ var c = equations[i]; if(c.timeStep !== h || c.needsUpdate){ c.timeStep = h; c.update(); } Bs[i] = c.computeB(c.a,c.b,h); invCs[i] = c.computeInvC(c.epsilon); } var q, B, c, deltalambdaTot,i,j; if(Neq !== 0){ // Reset vlambda for(i=0; i!==Nbodies; i++){ bodies[i].resetConstraintVelocity(); } if(maxFrictionIter){ // Iterate over contact equations to get normal forces for(iter=0; iter!==maxFrictionIter; iter++){ // Accumulate the total error for each iteration. deltalambdaTot = 0.0; for(j=0; j!==Neq; j++){ c = equations[j]; if(c instanceof FrictionEquation){ //continue; } var deltalambda = GSSolver.iterateEquation(j,c,c.epsilon,Bs,invCs,lambda,useZeroRHS,h,iter); deltalambdaTot += Math.abs(deltalambda); } this.usedIterations++; // If the total error is small enough - stop iterate if(deltalambdaTot*deltalambdaTot <= tolSquared){ break; } } GSSolver.updateMultipliers(equations, lambda, 1/h); // Set computed friction force for(j=0; j!==Neq; j++){ var eq = equations[j]; if(eq instanceof FrictionEquation){ var f = eq.contactEquation.multiplier * eq.frictionCoefficient; eq.maxForce = f; eq.minForce = -f; } } } // Iterate over all 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]; var deltalambda = GSSolver.iterateEquation(j,c,c.epsilon,Bs,invCs,lambda,useZeroRHS,h,iter); deltalambdaTot += Math.abs(deltalambda); } this.usedIterations++; // 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.updateMultipliers(equations, lambda, 1/h); } }; // Sets the .multiplier property of each equation GSSolver.updateMultipliers = function(equations, lambda, invDt){ // Set the .multiplier property of each equation var l = equations.length; while(l--){ equations[l].multiplier = lambda[l] * invDt; } }; GSSolver.iterateEquation = function(j,eq,eps,Bs,invCs,lambda,useZeroRHS,dt,iter){ // Compute iteration var B = Bs[j], invC = invCs[j], lambdaj = lambda[j], GWlambda = eq.computeGWlambda(); 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*dt){ deltalambda = minForce*dt - lambdaj; } else if(lambdaj_plus_deltalambda > maxForce*dt){ deltalambda = maxForce*dt - lambdaj; } lambda[j] += deltalambda; eq.addToWlambda(deltalambda); return deltalambda; }; },{"../equations/FrictionEquation":23,"../math/vec2":30,"../utils/Utils":47,"./Solver":44}],44:[function(require,module,exports){ var Utils = require('../utils/Utils') , EventEmitter = require('../events/EventEmitter') module.exports = Solver; /** * Base class for constraint solvers. * @class Solver * @constructor * @extends EventEmitter */ function Solver(options,type){ options = options || {}; EventEmitter.call(this); this.type = type; /** * Current equations in the solver. * * @property equations * @type {Array} */ this.equations = []; /** * Function that is used to sort all equations before each solve. * @property equationSortFunction * @type {function|boolean} */ this.equationSortFunction = options.equationSortFunction || false; } Solver.prototype = new EventEmitter(); /** * Method to be implemented in each subclass * @method solve * @param {Number} dt * @param {World} world */ Solver.prototype.solve = function(dt,world){ throw new Error("Solver.solve should be implemented by subclasses!"); }; var mockWorld = {bodies:[]}; /** * Solves all constraints in an island. * @method solveIsland * @param {Number} dt * @param {Island} island */ Solver.prototype.solveIsland = function(dt,island){ this.removeAllEquations(); if(island.equations.length){ // Add equations to solver this.addEquations(island.equations); mockWorld.bodies.length = 0; island.getBodies(mockWorld.bodies); // Solve if(mockWorld.bodies.length){ this.solve(dt,mockWorld); } } }; /** * Sort all equations using the .equationSortFunction. Should be called by subclasses before solving. * @method sortEquations */ Solver.prototype.sortEquations = function(){ if(this.equationSortFunction){ this.equations.sort(this.equationSortFunction); } }; /** * Add an equation to be solved. * * @method addEquation * @param {Equation} eq */ Solver.prototype.addEquation = function(eq){ if(eq.enabled){ this.equations.push(eq); } }; /** * Add equations. Same as .addEquation, but this time the argument is an array of Equations * * @method addEquations * @param {Array} eqs */ Solver.prototype.addEquations = function(eqs){ //Utils.appendArray(this.equations,eqs); for(var i=0, N=eqs.length; i!==N; i++){ var eq = eqs[i]; if(eq.enabled){ this.equations.push(eq); } } }; /** * Remove an equation. * * @method removeEquation * @param {Equation} eq */ Solver.prototype.removeEquation = function(eq){ var i = this.equations.indexOf(eq); if(i !== -1){ this.equations.splice(i,1); } }; /** * Remove all currently added equations. * * @method removeAllEquations */ Solver.prototype.removeAllEquations = function(){ this.equations.length=0; }; Solver.GS = 1; Solver.ISLAND = 2; },{"../events/EventEmitter":26,"../utils/Utils":47}],45:[function(require,module,exports){ var TupleDictionary = require('./TupleDictionary'); var Utils = require('./Utils'); module.exports = OverlapKeeper; /** * Keeps track of overlaps in the current state and the last step state. * @class OverlapKeeper * @constructor */ function OverlapKeeper() { this.overlappingLastState = new TupleDictionary(); this.overlappingCurrentState = new TupleDictionary(); this.recordPool = []; this.tmpDict = new TupleDictionary(); this.tmpArray1 = []; } /** * @method tick */ OverlapKeeper.prototype.tick = function() { var last = this.overlappingLastState; var current = this.overlappingCurrentState; // Save old objects into pool var l = current.keys.length; while(l--){ var key = current.keys[l]; this.recordPool.push(current.getByKey(key)); } // Clear last object last.reset(); // Transfer from new object to old last.copy(current); // Clear current object current.reset(); }; /** * @method setOverlapping */ OverlapKeeper.prototype.setOverlapping = function(bodyA, shapeA, bodyB, shapeB) { var last = this.overlappingLastState; var current = this.overlappingCurrentState; // Store current contact state if(!current.get(shapeA.id, shapeB.id)){ var data; if(this.recordPool.length){ data = this.recordPool.pop(); } else { data = new OverlapKeeperRecord(bodyA, shapeA, bodyB, shapeB); } current.set(shapeA.id, shapeB.id, data); } }; OverlapKeeper.prototype.getNewOverlaps = function(result){ return this.getDiff(this.overlappingLastState, this.overlappingCurrentState, result); }; OverlapKeeper.prototype.getEndOverlaps = function(result){ return this.getDiff(this.overlappingCurrentState, this.overlappingLastState, result); }; OverlapKeeper.prototype.getDiff = function(dictA, dictB, result){ var result = result || []; var last = dictA; var current = dictB; result.length = 0; var l = current.keys.length; while(l--){ var key = current.keys[l]; var data = current.data[key]; if(!data){ throw new Error('Key '+key+' had no data!'); } var lastData = last.data[key]; if(!lastData){ // Not overlapping in last state, but in current. result.push(data); } } return result; }; OverlapKeeper.prototype.isNewOverlap = function(shapeA, shapeB){ var idA = shapeA.id|0, idB = shapeB.id|0; return !!!this.overlappingLastState.get(idA, idB) && !!this.overlappingCurrentState.get(idA, idB); }; OverlapKeeper.prototype.getNewBodyOverlaps = function(result){ this.tmpArray1.length = 0; var overlaps = this.getNewOverlaps(this.tmpArray1); return this.getBodyDiff(overlaps, result); }; OverlapKeeper.prototype.getEndBodyOverlaps = function(result){ this.tmpArray1.length = 0; var overlaps = this.getEndOverlaps(this.tmpArray1); return this.getBodyDiff(overlaps, result); }; OverlapKeeper.prototype.getBodyDiff = function(overlaps, result){ result = result || []; var accumulator = this.tmpDict; var l = overlaps.length; while(l--){ var data = overlaps[l]; // Since we use body id's for the accumulator, these will be a subset of the original one accumulator.set(data.bodyA.id|0, data.bodyB.id|0, data); } l = accumulator.keys.length; while(l--){ var data = accumulator.keys[l]; result.push(data.bodyA, data.bodyB); } accumulator.reset(); return result; }; /** * Overlap data container for the OverlapKeeper * @param {Body} bodyA * @param {Shape} shapeA * @param {Body} bodyB * @param {Shape} shapeB [description] */ function OverlapKeeperRecord(bodyA, shapeA, bodyB, shapeB){ /** * @property {Shape} shapeA */ this.shapeA = shapeA; /** * @property {Shape} shapeB */ this.shapeB = shapeB; /** * @property {Body} bodyA */ this.bodyA = bodyA; /** * @property {Body} bodyB */ this.bodyB = bodyB; } OverlapKeeperRecord.prototype.set = function(bodyA, shapeA, bodyB, shapeB){ OverlapKeeperRecord.call(this, bodyA, shapeA, bodyB, shapeB); }; },{"./TupleDictionary":46,"./Utils":47}],46:[function(require,module,exports){ var Utils = require('./Utils'); module.exports = TupleDictionary; /** * @class TupleDictionary * @constructor */ function TupleDictionary() { /** * The data storage * @property data * @type {Array} */ this.data = []; /** * Keys that are currently used. * @type {Array} */ this.keys = []; } /** * Generate a key given two integers * @param {number} i * @param {number} j * @return {string} */ TupleDictionary.prototype.getKey = function(id1, id2) { id1 = id1|0; id2 = id2|0; if ( (id1|0) === (id2|0) ){ return -1; } // valid for values < 2^16 return ((id1|0) > (id2|0) ? (id1 << 16) | (id2 & 0xFFFF) : (id2 << 16) | (id1 & 0xFFFF))|0 ; }; /** * @method getByKey * @param {Number} key * @return {Object} */ TupleDictionary.prototype.getByKey = function(key) { key = key|0; return this.data[key]; }; /** * @method get * @param {Number} i * @param {Number} j * @return {Number} */ TupleDictionary.prototype.get = function(i, j) { i = i|0; j = j|0; var key = this.getKey(i, j)|0; return this.data[key]; }; /** * @method set * @param {Number} i * @param {Number} j * @param {Number} value */ TupleDictionary.prototype.set = function(i, j, value) { if(!value){ throw new Error("No data!"); } i = i|0; j = j|0; var key = this.getKey(i, j)|0; // Check if key already exists if(!this.get(i, j)){ this.keys.push(key); } this.data[key] = value; return key; }; /** * @method reset */ TupleDictionary.prototype.reset = function() { var data = this.data, keys = this.keys; var l = keys.length|0; while(l--){ var key = keys[l]|0; data[key] = undefined; } keys.length = 0; }; /** * @method copy */ TupleDictionary.prototype.copy = function(dict) { this.reset(); Utils.appendArray(this.keys, dict.keys); var l = dict.keys.length|0; while(l--){ var key = dict.keys[l]|0; this.data[key] = dict.data[key]; } }; },{"./Utils":47}],47:[function(require,module,exports){ module.exports = Utils; /** * Misc utility functions * @class Utils * @constructor */ function Utils(){}; /** * Append the values in array b to the array a. See this 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 = window.Float32Array || Array; /** * Extend an object with the properties of another * @static * @method extend * @param {object} a * @param {object} b */ Utils.extend = function(a,b){ for(var key in b){ a[key] = b[key]; } }; /** * Extend an object with the properties of another * @static * @method extend * @param {object} a * @param {object} b */ Utils.defaults = function(options, defaults){ options = options || {}; for(var key in defaults){ if(!(key in options)){ options[key] = defaults[key]; } } return options; }; },{}],48:[function(require,module,exports){ var Body = require('../objects/Body'); 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; }; var bodyIds = []; /** * Get all unique bodies in this island. * @method getBodies * @return {Array} An array of Body */ Island.prototype.getBodies = function(result){ var bodies = result || [], eqs = this.equations; bodyIds.length = 0; for(var i=0; i!==eqs.length; i++){ var eq = eqs[i]; if(bodyIds.indexOf(eq.bodyA.id)===-1){ bodies.push(eq.bodyA); bodyIds.push(eq.bodyA.id); } if(bodyIds.indexOf(eq.bodyB.id)===-1){ bodies.push(eq.bodyB); bodyIds.push(eq.bodyB.id); } } return bodies; }; /** * Check if the entire island wants to sleep. * @method wantsToSleep * @return {Boolean} */ Island.prototype.wantsToSleep = function(){ for(var i=0; i=0; i-=2){ for(var j=result.length-2; j>=0; j-=2){ if( (ignoredPairs[i] === result[j] && ignoredPairs[i+1] === result[j+1]) || (ignoredPairs[i+1] === result[j] && ignoredPairs[i] === result[j+1])){ result.splice(j,2); } } } // Remove constrained pairs with collideConnected == false var Nconstraints = constraints.length; for(i=0; i!==Nconstraints; i++){ var c = constraints[i]; if(!c.collideConnected){ for(var j=result.length-2; j>=0; j-=2){ if( (c.bodyA === result[j] && c.bodyB === result[j+1]) || (c.bodyB === result[j] && c.bodyA === result[j+1])){ result.splice(j,2); } } } } // postBroadphase event this.postBroadphaseEvent.pairs = result; this.emit(this.postBroadphaseEvent); // Narrowphase np.reset(this); for(var i=0, Nresults=result.length; i!==Nresults; i+=2){ var bi = result[i], bj = result[i+1]; // Loop over all shapes of body i for(var k=0, Nshapesi=bi.shapes.length; k!==Nshapesi; k++){ var si = bi.shapes[k], xi = bi.shapeOffsets[k], ai = bi.shapeAngles[k]; // All shapes of body j for(var l=0, Nshapesj=bj.shapes.length; l!==Nshapesj; l++){ var sj = bj.shapes[l], xj = bj.shapeOffsets[l], aj = bj.shapeAngles[l]; var cm = this.defaultContactMaterial; if(si.material && sj.material){ var tmp = this.getContactMaterial(si.material,sj.material); if(tmp){ cm = tmp; } } this.runNarrowphase(np,bi,si,xi,ai,bj,sj,xj,aj,cm,this.frictionGravity); } } } // Wake up bodies for(var i=0; i!==Nbodies; i++){ var body = bodies[i]; if(body._wakeUpAfterNarrowphase){ body.wakeUp(); body._wakeUpAfterNarrowphase = false; } } // Emit end overlap events if(this.has('endContact')){ this.overlapKeeper.getEndOverlaps(endOverlaps); var e = this.endContactEvent; var l = endOverlaps.length; while(l--){ var data = endOverlaps[l]; e.shapeA = data.shapeA; e.shapeB = data.shapeB; e.bodyA = data.bodyA; e.bodyB = data.bodyA; this.emit(e); } } this.overlapKeeper.tick(); var preSolveEvent = this.preSolveEvent; preSolveEvent.contactEquations = np.contactEquations; preSolveEvent.frictionEquations = np.frictionEquations; this.emit(preSolveEvent); // update constraint equations var Nconstraints = constraints.length; for(i=0; i!==Nconstraints; i++){ constraints[i].update(); } if(np.contactEquations.length || np.frictionEquations.length || constraints.length){ if(this.islandSplit){ // Split into islands islandManager.equations.length = 0; Utils.appendArray(islandManager.equations, np.contactEquations); Utils.appendArray(islandManager.equations, np.frictionEquations); for(i=0; i!==Nconstraints; i++){ Utils.appendArray(islandManager.equations, constraints[i].equations); } islandManager.split(this); for(var i=0; i!==islandManager.islands.length; i++){ var island = islandManager.islands[i]; if(island.equations.length){ solver.solveIsland(dt,island); } } } else { // Add contact equations to solver solver.addEquations(np.contactEquations); solver.addEquations(np.frictionEquations); // Add user-defined constraint equations for(i=0; i!==Nconstraints; i++){ solver.addEquations(constraints[i].equations); } if(this.solveConstraints){ solver.solve(dt,this); } solver.removeAllEquations(); } } // Step forward for(var i=0; i!==Nbodies; i++){ var body = bodies[i]; if(body.sleepState !== Body.SLEEPING && body.motionState !== Body.STATIC){ 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 && this.has('impact')){ var ev = this.impactEvent; for(var i=0; i!==np.contactEquations.length; i++){ var eq = np.contactEquations[i]; if(eq.firstImpact){ ev.bodyA = eq.bodyA; ev.bodyB = eq.bodyB; 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, false, dt); } } else if(this.enableIslandSleeping && this.islandSplit){ // Tell all bodies to sleep tick but dont sleep yet for(i=0; i!==Nbodies; i++){ bodies[i].sleepTick(this.time, true, dt); } // Sleep islands for(var i=0; i 0; np.frictionCoefficient = cm.friction; var reducedMass; if(bi.motionState === Body.STATIC || bi.motionState === Body.KINEMATIC){ reducedMass = bj.mass; } else if(bj.motionState === Body.STATIC || bj.motionState === Body.KINEMATIC){ reducedMass = bi.mass; } else { reducedMass = (bi.mass*bj.mass)/(bi.mass+bj.mass); } np.slipForce = cm.friction*glen*reducedMass; np.restitution = cm.restitution; np.surfaceVelocity = cm.surfaceVelocity; np.frictionStiffness = cm.frictionStiffness; np.frictionRelaxation = cm.frictionRelaxation; np.stiffness = cm.stiffness; np.relaxation = cm.relaxation; var resolver = np[si.type | sj.type], numContacts = 0; if (resolver) { var sensor = si.sensor || sj.sensor; var numFrictionBefore = np.frictionEquations.length; if (si.type < sj.type) { numContacts = resolver.call(np, bi,si,xiw,aiw, bj,sj,xjw,ajw, sensor); } else { numContacts = resolver.call(np, bj,sj,xjw,ajw, bi,si,xiw,aiw, sensor); } var numFrictionEquations = np.frictionEquations.length - numFrictionBefore; if(numContacts){ if( bi.allowSleep && bi.motionState === Body.DYNAMIC && bi.sleepState === Body.SLEEPING && bj.sleepState === Body.AWAKE && bj.motionState !== Body.STATIC ){ var speedSquaredB = vec2.squaredLength(bj.velocity) + Math.pow(bj.angularVelocity,2); var speedLimitSquaredB = Math.pow(bj.sleepSpeedLimit,2); if(speedSquaredB >= speedLimitSquaredB*2){ bi._wakeUpAfterNarrowphase = true; } } if( bj.allowSleep && bj.motionState === Body.DYNAMIC && bj.sleepState === Body.SLEEPING && bi.sleepState === Body.AWAKE && bi.motionState !== Body.STATIC ){ var speedSquaredA = vec2.squaredLength(bi.velocity) + Math.pow(bi.angularVelocity,2); var speedLimitSquaredA = Math.pow(bi.sleepSpeedLimit,2); if(speedSquaredA >= speedLimitSquaredA*2){ bj._wakeUpAfterNarrowphase = true; } } this.overlapKeeper.setOverlapping(bi, si, bj, sj); if(this.has('beginContact') && this.overlapKeeper.isNewOverlap(si, sj)){ // Report new shape overlap var e = this.beginContactEvent; e.shapeA = si; e.shapeB = sj; e.bodyA = bi; e.bodyB = bj; // Reset contact equations e.contactEquations.length = 0; if(typeof(numContacts)==="number"){ for(var i=np.contactEquations.length-numContacts; i 1){ // Why divide by 1? for(var i=np.frictionEquations.length-numFrictionEquations; 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]); } World.apply(this); }; /** * 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; }; /** * Sets the Equation parameters for all constraints and contact materials. * @method setGlobalEquationParameters * @param {object} [parameters] * @param {Number} [parameters.relaxation] * @param {Number} [parameters.stiffness] */ World.prototype.setGlobalEquationParameters = function(parameters){ parameters = parameters || {}; // Set for all constraints for(var i=0; i !== this.constraints.length; i++){ var c = this.constraints[i]; for(var j=0; j !== c.equations.length; j++){ var eq = c.equations[j]; if(typeof(parameters.stiffness) !== "undefined"){ eq.stiffness = parameters.stiffness; } if(typeof(parameters.relaxation) !== "undefined"){ eq.relaxation = parameters.relaxation; } eq.needsUpdate = true; } } // Set for all contact materials for(var i=0; i !== this.contactMaterials.length; i++){ var c = this.contactMaterials[i]; if(typeof(parameters.stiffness) !== "undefined"){ c.stiffness = parameters.stiffness; c.frictionStiffness = parameters.stiffness; } if(typeof(parameters.relaxation) !== "undefined"){ c.relaxation = parameters.relaxation; c.frictionRelaxation = parameters.relaxation; } } // Set for default contact material var c = this.defaultContactMaterial; if(typeof(parameters.stiffness) !== "undefined"){ c.stiffness = parameters.stiffness; c.frictionStiffness = parameters.stiffness; } if(typeof(parameters.relaxation) !== "undefined"){ c.relaxation = parameters.relaxation; c.frictionRelaxation = parameters.relaxation; } }; /** * Set the stiffness for all equations and contact materials. * @method setGlobalStiffness * @param {Number} stiffness */ World.prototype.setGlobalStiffness = function(stiffness){ this.setGlobalEquationParameters({ stiffness: stiffness }); }; /** * Set the relaxation for all equations and contact materials. * @method setGlobalRelaxation * @param {Number} relaxation */ World.prototype.setGlobalRelaxation = function(relaxation){ this.setGlobalEquationParameters({ relaxation: relaxation }); }; },{"../../package.json":7,"../collision/Broadphase":9,"../collision/NaiveBroadphase":11,"../collision/Narrowphase":12,"../collision/SAPBroadphase":13,"../constraints/Constraint":14,"../constraints/DistanceConstraint":15,"../constraints/GearConstraint":16,"../constraints/LockConstraint":17,"../constraints/PrismaticConstraint":18,"../constraints/RevoluteConstraint":19,"../events/EventEmitter":26,"../material/ContactMaterial":27,"../material/Material":28,"../math/vec2":30,"../objects/Body":31,"../objects/Spring":32,"../shapes/Capsule":34,"../shapes/Circle":35,"../shapes/Convex":36,"../shapes/Line":38,"../shapes/Particle":39,"../shapes/Plane":40,"../shapes/Rectangle":41,"../shapes/Shape":42,"../solver/GSSolver":43,"../solver/Solver":44,"../utils/OverlapKeeper":45,"../utils/Utils":47,"./IslandManager":49}]},{},[33]) (33) }); ;;