diff --git a/build/custom/p2.js b/build/custom/p2.js index 4e0777f12..38916e419 100644 --- a/build/custom/p2.js +++ b/build/custom/p2.js @@ -1,18 +1,18 @@ /** * 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 @@ -21,8 +21,8 @@ * 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 (http://steffe.se)", - "keywords": [ - "p2.js", - "p2", - "physics", - "engine", - "2d" - ], - "main": "./src/p2.js", - "engines": { - "node": "*" - }, - "repository": { - "type": "git", - "url": "https://github.com/schteppe/p2.js.git" - }, - "bugs": { - "url": "https://github.com/schteppe/p2.js/issues" - }, - "licenses" : [ - { - "type" : "MIT" - } - ], - "devDependencies" : { - "jshint" : "latest", - "nodeunit" : "latest", - "grunt": "~0.4.0", - "grunt-contrib-jshint": "~0.1.1", - "grunt-contrib-nodeunit": "~0.1.2", - "grunt-contrib-concat": "~0.1.3", - "grunt-contrib-uglify": "*", - "grunt-browserify" : "*", - "browserify":"*" - }, - "dependencies" : { - "underscore":"*", - "poly-decomp" : "git://github.com/schteppe/poly-decomp.js", - "gl-matrix":"2.0.0", - "jsonschema":"*" + "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-browserify": "~2.0.1", + "z-schema": "~2.4.6" + }, + "dependencies": { + "poly-decomp": "git://github.com/schteppe/poly-decomp.js", + "gl-matrix": "2.1.0" + } } -},{}],9:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ var vec2 = require('../math/vec2') -, Utils = require('../utils/Utils') +, Utils = require('../utils/Utils'); module.exports = AABB; @@ -1421,9 +1271,9 @@ module.exports = AABB; * Axis aligned bounding box class. * @class AABB * @constructor - * @param {Object} options - * @param {Array} upperBound - * @param {Array} lowerBound + * @param {Object} [options] + * @param {Array} [options.upperBound] + * @param {Array} [options.lowerBound] */ function AABB(options){ @@ -1433,7 +1283,9 @@ function AABB(options){ * @type {Array} */ this.lowerBound = vec2.create(); - if(options && options.lowerBound) vec2.copy(this.lowerBound, options.lowerBound); + if(options && options.lowerBound){ + vec2.copy(this.lowerBound, options.lowerBound); + } /** * The upper bound of the bounding box. @@ -1441,7 +1293,9 @@ function AABB(options){ * @type {Array} */ this.upperBound = vec2.create(); - if(options && options.upperBound) vec2.copy(this.upperBound, options.upperBound); + if(options && options.upperBound){ + vec2.copy(this.upperBound, options.upperBound); + } } var tmp = vec2.create(); @@ -1459,7 +1313,7 @@ AABB.prototype.setFromPoints = function(points,position,angle){ for(var i=0; i this.upperBound[i]) + if(aabb.upperBound[i] > this.upperBound[i]){ this.upperBound[i] = aabb.upperBound[i]; + } } }; @@ -1530,7 +1386,7 @@ AABB.prototype.overlaps = function(aabb){ ((l2[1] <= u1[1] && u1[1] <= u2[1]) || (l1[1] <= u2[1] && u2[1] <= u1[1])); }; -},{"../math/vec2":33,"../utils/Utils":50}],10:[function(require,module,exports){ +},{"../math/vec2":30,"../utils/Utils":45}],9:[function(require,module,exports){ var vec2 = require('../math/vec2') var Body = require('../objects/Body') @@ -1556,9 +1412,30 @@ function Broadphase(type){ * 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 @@ -1603,11 +1480,38 @@ Broadphase.boundingRadiusCheck = function(bodyA, bodyB){ * @return {Boolean} */ Broadphase.aabbCheck = function(bodyA, bodyB){ - if(bodyA.aabbNeedsUpdate) bodyA.updateAABB(); - if(bodyB.aabbNeedsUpdate) bodyB.updateAABB(); + 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 @@ -1618,21 +1522,31 @@ Broadphase.aabbCheck = function(bodyA, bodyB){ Broadphase.canCollide = function(bodyA, bodyB){ // Cannot collide static bodies - if(bodyA.motionState == Body.STATIC && bodyB.motionState == Body.STATIC) + 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)) + 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) + 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) + 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; }; @@ -1640,12 +1554,13 @@ Broadphase.canCollide = function(bodyA, bodyB){ Broadphase.NAIVE = 1; Broadphase.SAP = 2; -},{"../math/vec2":33,"../objects/Body":34}],11:[function(require,module,exports){ +},{"../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; @@ -1654,50 +1569,39 @@ module.exports = GridBroadphase; * @class GridBroadphase * @constructor * @extends Broadphase - * @param {number} xmin Lower x bound of the grid - * @param {number} xmax Upper x bound - * @param {number} ymin Lower y bound - * @param {number} ymax Upper y bound - * @param {number} nx Number of bins along x axis - * @param {number} ny Number of bins along y axis - * @todo test + * @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(xmin,xmax,ymin,ymax,nx,ny){ +function GridBroadphase(options){ + options = options || {}; Broadphase.apply(this); - nx = nx || 10; - ny = ny || 10; + Utils.extend(options,{ + xmin: -100, + xmax: 100, + ymin: -100, + ymax: 100, + nx: 10, + ny: 10 + }); - this.binsizeX = (xmax-xmin) / nx; - this.binsizeY = (ymax-ymin) / ny; - this.nx = nx; - this.ny = ny; - this.xmin = xmin; - this.ymin = ymin; - this.xmax = xmax; - this.ymax = ymax; -}; -GridBroadphase.prototype = new Broadphase(); + this.xmin = options.xmin; + this.ymin = options.ymin; + this.xmax = options.xmax; + this.ymax = options.ymax; + this.nx = options.nx; + this.ny = options.ny; -/** - * Get a bin index given a world coordinate - * @method getBinIndex - * @param {Number} x - * @param {Number} y - * @return {Number} Integer index - */ -GridBroadphase.prototype.getBinIndex = function(x,y){ - var nx = this.nx, - ny = this.ny, - xmin = this.xmin, - ymin = this.ymin, - xmax = this.xmax, - ymax = this.ymax; - - var xi = Math.floor(nx * (x - xmin) / (xmax-xmin)); - var yi = Math.floor(ny * (y - ymin) / (ymax-ymin)); - return xi*ny + yi; + this.binsizeX = (this.xmax-this.xmin) / this.nx; + this.binsizeY = (this.ymax-this.ymin) / this.ny; } +GridBroadphase.prototype = new Broadphase(); /** * Get collision pairs. @@ -1707,70 +1611,49 @@ GridBroadphase.prototype.getBinIndex = function(x,y){ */ GridBroadphase.prototype.getCollisionPairs = function(world){ var result = [], - collidingBodies = world.bodies, - Ncolliding = Ncolliding=collidingBodies.length, + bodies = world.bodies, + Ncolliding = bodies.length, binsizeX = this.binsizeX, - binsizeY = this.binsizeY; + 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 && xi*(ny-1) + yi < Nbins) - bins[ xi*(ny-1) + yi ].push(bi); + // Put in bin + for(var j=xi1; j<=xi2; j++){ + for(var k=yi1; k<=yi2; k++){ + var xi = j; + var yi = k; + var idx = xi*(ny-1) + yi; + if(idx >= 0 && idx < Nbins){ + bins[ idx ].push(bi); } } - } else if(si instanceof Plane){ - // Put in all bins for now - if(bi.angle == 0){ - var y = bi.position[1]; - for(var j=0; j!==Nbins && ymin+binsizeY*(j-1) id2){ var tmp = id1; id1 = id2; @@ -2062,21 +1927,17 @@ Narrowphase.prototype.reset = function(world){ */ Narrowphase.prototype.createContactEquation = function(bodyA,bodyB,shapeA,shapeB){ var c = this.reusableContactEquations.length ? this.reusableContactEquations.pop() : new ContactEquation(bodyA,bodyB); - c.bi = bodyA; - c.bj = bodyB; + c.bodyA = bodyA; + c.bodyB = bodyB; c.shapeA = shapeA; c.shapeB = shapeB; c.restitution = this.restitution; c.firstImpact = !this.collidedLastStep(bodyA,bodyB); c.stiffness = this.stiffness; c.relaxation = this.relaxation; + c.needsUpdate = true; c.enabled = true; - if(bodyA.allowSleep && (bodyA.motionState == Body.DYNAMIC) && !(bodyB.motionState == Body.STATIC || bodyB.sleepState === Body.SLEEPY)) - bodyA.wakeUp(); - if(bodyB.allowSleep && (bodyB.motionState == Body.DYNAMIC) && !(bodyA.motionState == Body.STATIC || bodyA.sleepState === Body.SLEEPY)) - bodyB.wakeUp(); - return c; }; @@ -2089,16 +1950,17 @@ Narrowphase.prototype.createContactEquation = function(bodyA,bodyB,shapeA,shapeB */ Narrowphase.prototype.createFrictionEquation = function(bodyA,bodyB,shapeA,shapeB){ var c = this.reusableFrictionEquations.length ? this.reusableFrictionEquations.pop() : new FrictionEquation(bodyA,bodyB); - c.bi = bodyA; - c.bj = bodyB; + c.bodyA = bodyA; + c.bodyB = bodyB; c.shapeA = shapeA; c.shapeB = shapeB; c.setSlipForce(this.slipForce); c.frictionCoefficient = this.frictionCoefficient; c.relativeVelocity = this.surfaceVelocity; c.enabled = true; - c.frictionStiffness = this.frictionStiffness; - c.frictionRelaxation = this.frictionRelaxation; + c.needsUpdate = true; + c.stiffness = this.frictionStiffness; + c.relaxation = this.frictionRelaxation; return c; }; @@ -2109,13 +1971,13 @@ Narrowphase.prototype.createFrictionEquation = function(bodyA,bodyB,shapeA,shape * @return {FrictionEquation} */ Narrowphase.prototype.createFrictionFromContact = function(c){ - var eq = this.createFrictionEquation(c.bi,c.bj,c.shapeA,c.shapeB); - vec2.copy(eq.ri, c.ri); - vec2.copy(eq.rj, c.rj); - vec2.rotate(eq.t, c.ni, -Math.PI / 2); + var eq = this.createFrictionEquation(c.bodyA, c.bodyB, c.shapeA, c.shapeB); + vec2.copy(eq.contactPointA, c.contactPointA); + vec2.copy(eq.contactPointB, c.contactPointB); + vec2.rotate(eq.t, c.normalA, -Math.PI / 2); eq.contactEquation = c; return eq; -} +}; /** * Convex/line narrowphase @@ -2390,20 +2252,20 @@ Narrowphase.prototype.planeLine = function(planeBody, planeShape, planeOffset, p var c = this.createContactEquation(planeBody,lineBody,planeShape,lineShape); numContacts++; - vec2.copy(c.ni, worldNormal); - vec2.normalize(c.ni,c.ni); + vec2.copy(c.normalA, worldNormal); + vec2.normalize(c.normalA,c.normalA); // distance vector along plane normal vec2.scale(dist, worldNormal, d); // Vector from plane center to contact - sub(c.ri, v, dist); - sub(c.ri, c.ri, planeBody.position); + sub(c.contactPointA, v, dist); + sub(c.contactPointA, c.contactPointA, planeBody.position); // From line center to contact - sub(c.rj, v, lineOffset); - add(c.rj, c.rj, lineOffset); - sub(c.rj, c.rj, lineBody.position); + sub(c.contactPointB, v, lineOffset); + add(c.contactPointB, c.contactPointB, lineOffset); + sub(c.contactPointB, c.contactPointB, lineBody.position); this.contactEquations.push(c); @@ -2519,16 +2381,16 @@ Narrowphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, var c = this.createContactEquation(circleBody,lineBody,si,sj); - vec2.scale(c.ni, orthoDist, -1); - vec2.normalize(c.ni, c.ni); + vec2.scale(c.normalA, orthoDist, -1); + vec2.normalize(c.normalA, c.normalA); - vec2.scale( c.ri, c.ni, circleRadius); - add(c.ri, c.ri, circleOffset); - sub(c.ri, c.ri, circleBody.position); + vec2.scale( c.contactPointA, c.normalA, circleRadius); + add(c.contactPointA, c.contactPointA, circleOffset); + sub(c.contactPointA, c.contactPointA, circleBody.position); - sub(c.rj, projectedPoint, lineOffset); - add(c.rj, c.rj, lineOffset); - sub(c.rj, c.rj, lineBody.position); + sub(c.contactPointB, projectedPoint, lineOffset); + add(c.contactPointB, c.contactPointB, lineOffset); + sub(c.contactPointB, c.contactPointB, lineBody.position); this.contactEquations.push(c); @@ -2556,19 +2418,19 @@ Narrowphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, var c = this.createContactEquation(circleBody,lineBody,si,sj); - vec2.copy(c.ni, dist); - vec2.normalize(c.ni,c.ni); + 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.ri, c.ni, circleRadius); - add(c.ri, c.ri, circleOffset); - sub(c.ri, c.ri, circleBody.position); + vec2.scale(c.contactPointA, c.normalA, circleRadius); + add(c.contactPointA, c.contactPointA, circleOffset); + sub(c.contactPointA, c.contactPointA, circleBody.position); - sub(c.rj, v, lineOffset); - vec2.scale(lineEndToLineRadius, c.ni, -lineRadius); - add(c.rj, c.rj, lineEndToLineRadius); - add(c.rj, c.rj, lineOffset); - sub(c.rj, c.rj, lineBody.position); + sub(c.contactPointB, v, lineOffset); + vec2.scale(lineEndToLineRadius, c.normalA, -lineRadius); + add(c.contactPointB, c.contactPointB, lineEndToLineRadius); + add(c.contactPointB, c.contactPointB, lineOffset); + sub(c.contactPointB, c.contactPointB, lineBody.position); this.contactEquations.push(c); @@ -2733,16 +2595,16 @@ Narrowphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe return true; var c = this.createContactEquation(circleBody,convexBody,si,sj); - vec2.sub(c.ni, minCandidate, circleOffset) - vec2.normalize(c.ni, c.ni); + vec2.sub(c.normalA, minCandidate, circleOffset) + vec2.normalize(c.normalA, c.normalA); - vec2.scale(c.ri, c.ni, circleRadius); - add(c.ri, c.ri, circleOffset); - sub(c.ri, c.ri, circleBody.position); + vec2.scale(c.contactPointA, c.normalA, circleRadius); + add(c.contactPointA, c.contactPointA, circleOffset); + sub(c.contactPointA, c.contactPointA, circleBody.position); - sub(c.rj, closestEdgeProjectedPoint, convexOffset); - add(c.rj, c.rj, convexOffset); - sub(c.rj, c.rj, convexBody.position); + sub(c.contactPointB, closestEdgeProjectedPoint, convexOffset); + add(c.contactPointB, c.contactPointB, convexOffset); + sub(c.contactPointB, c.contactPointB, convexBody.position); this.contactEquations.push(c); @@ -2756,16 +2618,16 @@ Narrowphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe if(closestEdge != -1){ var c = this.createContactEquation(circleBody,convexBody); - vec2.scale(c.ni, closestEdgeOrthoDist, -1); - vec2.normalize(c.ni, c.ni); + vec2.scale(c.normalA, closestEdgeOrthoDist, -1); + vec2.normalize(c.normalA, c.normalA); - vec2.scale(c.ri, c.ni, circleRadius); - add(c.ri, c.ri, circleOffset); - sub(c.ri, c.ri, circleBody.position); + vec2.scale(c.contactPointA, c.normalA, circleRadius); + add(c.contactPointA, c.contactPointA, circleOffset); + sub(c.contactPointA, c.contactPointA, circleBody.position); - sub(c.rj, closestEdgeProjectedPoint, convexOffset); - add(c.rj, c.rj, convexOffset); - sub(c.rj, c.rj, convexBody.position); + sub(c.contactPointB, closestEdgeProjectedPoint, convexOffset); + add(c.contactPointB, c.contactPointB, convexOffset); + sub(c.contactPointB, c.contactPointB, convexBody.position); this.contactEquations.push(c); @@ -2790,17 +2652,17 @@ Narrowphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe var c = this.createContactEquation(circleBody,convexBody,si,sj); - vec2.copy(c.ni, dist); - vec2.normalize(c.ni,c.ni); + 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.ri, c.ni, circleRadius); - add(c.ri, c.ri, circleOffset); - sub(c.ri, c.ri, circleBody.position); + vec2.scale(c.contactPointA, c.normalA, circleRadius); + add(c.contactPointA, c.contactPointA, circleOffset); + sub(c.contactPointA, c.contactPointA, circleBody.position); - sub(c.rj, worldVertex, convexOffset); - add(c.rj, c.rj, convexOffset); - sub(c.rj, c.rj, convexBody.position); + sub(c.contactPointB, worldVertex, convexOffset); + add(c.contactPointB, c.contactPointB, convexOffset); + sub(c.contactPointB, c.contactPointB, convexBody.position); this.contactEquations.push(c); @@ -2978,18 +2840,18 @@ Narrowphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, just if(found){ var c = this.createContactEquation(particleBody,convexBody,si,sj); - vec2.scale(c.ni, minEdgeNormal, -1); - vec2.normalize(c.ni, c.ni); + vec2.scale(c.normalA, minEdgeNormal, -1); + vec2.normalize(c.normalA, c.normalA); // Particle has no extent to the contact point - vec2.set(c.ri, 0, 0); - add(c.ri, c.ri, particleOffset); - sub(c.ri, c.ri, particleBody.position); + vec2.set(c.contactPointA, 0, 0); + add(c.contactPointA, c.contactPointA, particleOffset); + sub(c.contactPointA, c.contactPointA, particleBody.position); // From convex center to point - sub(c.rj, closestEdgeProjectedPoint, convexOffset); - add(c.rj, c.rj, convexOffset); - sub(c.rj, c.rj, convexBody.position); + sub(c.contactPointB, closestEdgeProjectedPoint, convexOffset); + add(c.contactPointB, c.contactPointB, convexOffset); + sub(c.contactPointB, c.contactPointB, convexBody.position); this.contactEquations.push(c); @@ -3029,23 +2891,26 @@ Narrowphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTe sub(dist,xi,xj); var r = radiusA + radiusB; - if(vec2.squaredLength(dist) > r*r) + if(vec2.squaredLength(dist) > r*r){ return 0; + } - if(justTest) return true; + if(justTest){ + return true; + } var c = this.createContactEquation(bodyA,bodyB,si,sj); - sub(c.ni, offsetB, offsetA); - vec2.normalize(c.ni,c.ni); + sub(c.normalA, offsetB, offsetA); + vec2.normalize(c.normalA,c.normalA); - vec2.scale( c.ri, c.ni, radiusA); - vec2.scale( c.rj, c.ni, -radiusB); + vec2.scale( c.contactPointA, c.normalA, radiusA); + vec2.scale( c.contactPointB, c.normalA, -radiusB); - add(c.ri, c.ri, offsetA); - sub(c.ri, c.ri, bodyA.position); + add(c.contactPointA, c.contactPointA, offsetA); + sub(c.contactPointA, c.contactPointA, bodyA.position); - add(c.rj, c.rj, offsetB); - sub(c.rj, c.rj, bodyB.position); + add(c.contactPointB, c.contactPointB, offsetB); + sub(c.contactPointB, c.contactPointB, bodyB.position); this.contactEquations.push(c); @@ -3093,9 +2958,11 @@ Narrowphase.prototype.planeConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest sub(dist, worldVertex, planeOffset); - if(dot(dist,worldNormal) < 0){ + if(dot(dist,worldNormal) <= Narrowphase.convexPrecision){ - if(justTest) return true; + if(justTest){ + return true; + } // Found vertex numReported++; @@ -3104,29 +2971,23 @@ Narrowphase.prototype.planeConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest sub(dist, worldVertex, planeOffset); - vec2.copy(c.ni, worldNormal); + vec2.copy(c.normalA, worldNormal); - var d = dot(dist, c.ni); - vec2.scale(dist, c.ni, d); + var d = dot(dist, c.normalA); + vec2.scale(dist, c.normalA, d); // rj is from convex center to contact - sub(c.rj, worldVertex, convexBody.position); + sub(c.contactPointB, worldVertex, convexBody.position); // ri is from plane center to contact - sub( c.ri, worldVertex, dist); - sub( c.ri, c.ri, planeBody.position); + sub( c.contactPointA, worldVertex, dist); + sub( c.contactPointA, c.contactPointA, planeBody.position); this.contactEquations.push(c); - - // TODO: if we have 2 contacts, we do only need 1 friction equation - if(this.enableFriction){ this.frictionEquations.push(this.createFrictionFromContact(c)); } - - if(numReported >= 2) - break; } } @@ -3179,16 +3040,16 @@ Narrowphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTe var c = this.createContactEquation(planeBody,particleBody,sj,si); - vec2.copy(c.ni, worldNormal); - vec2.scale( dist, c.ni, d ); + 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.ri, particleOffset, dist); - sub( c.ri, c.ri, planeBody.position); + sub( c.contactPointA, particleOffset, dist); + sub( c.contactPointA, c.contactPointA, planeBody.position); // rj is from the body center to the particle center - sub( c.rj, particleOffset, particleBody.position ); + sub( c.contactPointB, particleOffset, particleBody.position ); this.contactEquations.push(c); @@ -3225,16 +3086,16 @@ Narrowphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, jus if(justTest) return true; var c = this.createContactEquation(circleBody,particleBody,si,sj); - vec2.copy(c.ni, dist); - vec2.normalize(c.ni,c.ni); + 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.ri, c.ni, circleShape.radius); - add(c.ri, c.ri, circleOffset); - sub(c.ri, c.ri, circleBody.position); + 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.rj, particleOffset, particleBody.position); + sub(c.contactPointB, particleOffset, particleBody.position); this.contactEquations.push(c); @@ -3323,26 +3184,30 @@ Narrowphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTe // Normal direction distance var d = dot(worldNormal, planeToCircle); - if(d > circleShape.radius) return 0; // No overlap. Abort. + if(d > circleShape.radius){ + return 0; // No overlap. Abort. + } - if(justTest) return true; + if(justTest){ + return true; + } // Create contact var contact = this.createContactEquation(planeBody,circleBody,sj,si); // ni is the plane world normal - vec2.copy(contact.ni, worldNormal); + vec2.copy(contact.normalA, worldNormal); // rj is the vector from circle center to the contact point - vec2.scale(contact.rj, contact.ni, -circleShape.radius); - add(contact.rj, contact.rj, circleOffset); - sub(contact.rj, contact.rj, circleBody.position); + 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.ni, d); - sub(contact.ri, planeToCircle, temp ); // Subtract normal distance vector from the distance vector - add(contact.ri, contact.ri, planeOffset); - sub(contact.ri, contact.ri, planeBody.position); + 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); @@ -3353,7 +3218,7 @@ Narrowphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTe return 1; }; -Narrowphase.convexPrecision = 1e-10; +Narrowphase.convexPrecision = 1e-7; /** * Convex/convex Narrowphase.See this article for more info. @@ -3384,7 +3249,9 @@ Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe precision = precision || Narrowphase.convexPrecision; var found = Narrowphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis); - if(!found) return 0; + if(!found){ + return 0; + } // Make sure the separating axis is directed from shape i to shape j sub(dist,xj,xi); @@ -3396,7 +3263,9 @@ Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe var closestEdge1 = Narrowphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis closestEdge2 = Narrowphase.getClosestEdge(sj,aj,sepAxis); - if(closestEdge1==-1 || closestEdge2==-1) return 0; + if(closestEdge1 === -1 || closestEdge2 === -1){ + return 0; + } // Loop over the shapes for(var k=0; k<2; k++){ @@ -3408,7 +3277,7 @@ Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe angleA = ai, angleB = aj, bodyA = bi, bodyB = bj; - if(k==0){ + if(k === 0){ // Swap! var tmp; tmp = closestEdgeA; closestEdgeA = closestEdgeB; closestEdgeB = tmp; @@ -3454,9 +3323,11 @@ Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe } } - if(insideNumEdges == 3){ + if(insideNumEdges >= 3){ - if(justTest) return true; + 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 @@ -3477,21 +3348,21 @@ Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe sub(worldEdge, worldPoint1, worldPoint0); - vec2.rotate(c.ni, worldEdge, -Math.PI/2); // Normal points out of convex A - vec2.normalize(c.ni,c.ni); + vec2.rotate(c.normalA, worldEdge, -Math.PI/2); // 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.ni,dist); // Penetration - vec2.scale(penetrationVec, c.ni, d); // Vector penetration + var d = dot(c.normalA,dist); // Penetration + vec2.scale(penetrationVec, c.normalA, d); // Vector penetration - sub(c.ri, worldPoint, offsetA); - sub(c.ri, c.ri, penetrationVec); - add(c.ri, c.ri, offsetA); - sub(c.ri, c.ri, bodyA.position); + 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.rj, worldPoint, offsetB); - add(c.rj, c.rj, offsetB); - sub(c.rj, c.rj, bodyB.position); + sub(c.contactPointB, worldPoint, offsetB); + add(c.contactPointB, c.contactPointB, offsetB); + sub(c.contactPointB, c.contactPointB, bodyB.position); this.contactEquations.push(c); @@ -3615,7 +3486,7 @@ Narrowphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,se // Get separating distance var dist = b[0] - a[1]; - overlap = dist < 0; + overlap = (dist <= Narrowphase.convexPrecision); if(maxDist===null || dist > maxDist){ vec2.copy(sepAxis, normal); @@ -3787,16 +3658,16 @@ Narrowphase.prototype.circleHeightfield = function( circleBody,circleShape,circl var c = this.createContactEquation(hfBody,circleBody,hfShape,circleShape); // Normal is out of the heightfield - vec2.copy(c.ni, minCandidateNormal); + vec2.copy(c.normalA, minCandidateNormal); // Vector from circle to heightfield - vec2.scale(c.rj, c.ni, -radius); - add(c.rj, c.rj, circlePos); - sub(c.rj, c.rj, circleBody.position); + vec2.scale(c.contactPointB, c.normalA, -radius); + add(c.contactPointB, c.contactPointB, circlePos); + sub(c.contactPointB, c.contactPointB, circleBody.position); - vec2.copy(c.ri, minCandidate); - //vec2.sub(c.ri, c.ri, hfPos); - vec2.sub(c.ri, c.ri, hfBody.position); + vec2.copy(c.contactPointA, minCandidate); + //vec2.sub(c.contactPointA, c.contactPointA, hfPos); + vec2.sub(c.contactPointA, c.contactPointA, hfBody.position); this.contactEquations.push(c); @@ -3824,16 +3695,16 @@ Narrowphase.prototype.circleHeightfield = function( circleBody,circleShape,circl var c = this.createContactEquation(hfBody,circleBody,hfShape,circleShape); // Construct normal - out of heightfield - vec2.copy(c.ni, dist); - vec2.normalize(c.ni,c.ni); + vec2.copy(c.normalA, dist); + vec2.normalize(c.normalA,c.normalA); - vec2.scale(c.rj, c.ni, -radius); - add(c.rj, c.rj, circlePos); - sub(c.rj, c.rj, circleBody.position); + vec2.scale(c.contactPointB, c.normalA, -radius); + add(c.contactPointB, c.contactPointB, circlePos); + sub(c.contactPointB, c.contactPointB, circleBody.position); - sub(c.ri, v0, hfPos); - add(c.ri, c.ri, hfPos); - sub(c.ri, c.ri, hfBody.position); + sub(c.contactPointA, v0, hfPos); + add(c.contactPointA, c.contactPointA, hfPos); + sub(c.contactPointA, c.contactPointA, hfBody.position); this.contactEquations.push(c); @@ -3850,385 +3721,7 @@ Narrowphase.prototype.circleHeightfield = function( circleBody,circleShape,circl }; -},{"../equations/ContactEquation":23,"../equations/FrictionEquation":25,"../math/vec2":33,"../objects/Body":34,"../shapes/Circle":38,"../shapes/Rectangle":44,"../shapes/Shape":45,"../utils/Utils":50}],14:[function(require,module,exports){ -var Plane = require("../shapes/Plane"); -var Broadphase = require("../collision/Broadphase"); - -module.exports = { - QuadTree : QuadTree, - Node : Node, - BoundsNode : BoundsNode, -}; - -/** - * QuadTree data structure. See https://github.com/mikechambers/ExamplesByMesh/tree/master/JavaScript/QuadTree - * @class QuadTree - * @constructor - * @param {Object} An object representing the bounds of the top level of the QuadTree. The object - * should contain the following properties : x, y, width, height - * @param {Boolean} pointQuad Whether the QuadTree will contain points (true), or items with bounds - * (width / height)(false). Default value is false. - * @param {Number} maxDepth The maximum number of levels that the quadtree will create. Default is 4. - * @param {Number} maxChildren The maximum number of children that a node can contain before it is split into sub-nodes. - */ -function QuadTree(bounds, pointQuad, maxDepth, maxChildren){ - var node; - if(pointQuad){ - node = new Node(bounds, 0, maxDepth, maxChildren); - } else { - node = new BoundsNode(bounds, 0, maxDepth, maxChildren); - } - - /** - * The root node of the QuadTree which covers the entire area being segmented. - * @property root - * @type Node - */ - this.root = node; -} - -/** - * Inserts an item into the QuadTree. - * @method insert - * @param {Object|Array} item The item or Array of items to be inserted into the QuadTree. The item should expose x, y - * properties that represents its position in 2D space. - */ -QuadTree.prototype.insert = function(item){ - if(item instanceof Array){ - var len = item.length; - for(var i = 0; i < len; i++){ - this.root.insert(item[i]); - } - } else { - this.root.insert(item); - } -} - -/** - * Clears all nodes and children from the QuadTree - * @method clear - */ -QuadTree.prototype.clear = function(){ - this.root.clear(); -} - -/** - * Retrieves all items / points in the same node as the specified item / point. If the specified item - * overlaps the bounds of a node, then all children in both nodes will be returned. - * @method retrieve - * @param {Object} item An object representing a 2D coordinate point (with x, y properties), or a shape - * with dimensions (x, y, width, height) properties. - */ -QuadTree.prototype.retrieve = function(item){ - //get a copy of the array of items - var out = this.root.retrieve(item).slice(0); - return out; -} - -QuadTree.prototype.getCollisionPairs = function(world){ - - var result = []; - - // Add all bodies - this.insert(world.bodies); - - /* - console.log("bodies",world.bodies.length); - console.log("maxDepth",this.root.maxDepth,"maxChildren",this.root.maxChildren); - */ - - for(var i=0; i!==world.bodies.length; i++){ - var b = world.bodies[i], - items = this.retrieve(b); - - //console.log("items",items.length); - - // Check results - for(var j=0, len=items.length; j!==len; j++){ - var item = items[j]; - - if(b === item) continue; // Do not add self - - // Check if they were already added - var found = false; - for(var k=0, numAdded=result.length; k= this.maxDepth) && len > this.maxChildren) { - this.subdivide(); - - for(var i = 0; i < len; i++){ - this.insert(this.children[i]); - } - - this.children.length = 0; - } -} - -Node.prototype.retrieve = function(item){ - if(this.nodes.length){ - var index = this.findIndex(item); - return this.nodes[index].retrieve(item); - } - - return this.children; -} - -Node.prototype.findIndex = function(item){ - var b = this.bounds; - var left = (item.position[0]-item.boundingRadius > b.x + b.width / 2) ? false : true; - var top = (item.position[1]-item.boundingRadius > b.y + b.height / 2) ? false : true; - - if(item instanceof Plane){ - left = top = false; // Will overlap the left/top boundary since it is infinite - } - - //top left - var index = Node.TOP_LEFT; - if(left){ - if(!top){ - index = Node.BOTTOM_LEFT; - } - } else { - if(top){ - index = Node.TOP_RIGHT; - } else { - index = Node.BOTTOM_RIGHT; - } - } - - return index; -} - - -Node.prototype.subdivide = function(){ - var depth = this.depth + 1; - - var bx = this.bounds.x; - var by = this.bounds.y; - - //floor the values - var b_w_h = (this.bounds.width / 2); - var b_h_h = (this.bounds.height / 2); - var bx_b_w_h = bx + b_w_h; - var by_b_h_h = by + b_h_h; - - //top left - this.nodes[Node.TOP_LEFT] = new this.classConstructor({ - x:bx, - y:by, - width:b_w_h, - height:b_h_h - }, - depth); - - //top right - this.nodes[Node.TOP_RIGHT] = new this.classConstructor({ - x:bx_b_w_h, - y:by, - width:b_w_h, - height:b_h_h - }, - depth); - - //bottom left - this.nodes[Node.BOTTOM_LEFT] = new this.classConstructor({ - x:bx, - y:by_b_h_h, - width:b_w_h, - height:b_h_h - }, - depth); - - - //bottom right - this.nodes[Node.BOTTOM_RIGHT] = new this.classConstructor({ - x:bx_b_w_h, - y:by_b_h_h, - width:b_w_h, - height:b_h_h - }, - depth); -} - -Node.prototype.clear = function(){ - this.children.length = 0; - - var len = this.nodes.length; - for(var i = 0; i < len; i++){ - this.nodes[i].clear(); - } - - this.nodes.length = 0; -} - - -// BoundsQuadTree - -function BoundsNode(bounds, depth, maxChildren, maxDepth){ - Node.call(this, bounds, depth, maxChildren, maxDepth); - this.stuckChildren = []; -} - -BoundsNode.prototype = new Node(); -BoundsNode.prototype.classConstructor = BoundsNode; -BoundsNode.prototype.stuckChildren = null; - -//we use this to collect and conctenate items being retrieved. This way -//we dont have to continuously create new Array instances. -//Note, when returned from QuadTree.retrieve, we then copy the array -BoundsNode.prototype.out = []; - -BoundsNode.prototype.insert = function(item){ - if(this.nodes.length){ - var index = this.findIndex(item); - var node = this.nodes[index]; - - /* - console.log("radius:",item.boundingRadius); - console.log("item x:",item.position[0] - item.boundingRadius,"x range:",node.bounds.x,node.bounds.x+node.bounds.width); - console.log("item y:",item.position[1] - item.boundingRadius,"y range:",node.bounds.y,node.bounds.y+node.bounds.height); - */ - - //todo: make _bounds bounds - if( !(item instanceof Plane) && // Plane is infinite.. Make it a "stuck" child - item.position[0] - item.boundingRadius >= node.bounds.x && - item.position[0] + item.boundingRadius <= node.bounds.x + node.bounds.width && - item.position[1] - item.boundingRadius >= node.bounds.y && - item.position[1] + item.boundingRadius <= node.bounds.y + node.bounds.height){ - this.nodes[index].insert(item); - } else { - this.stuckChildren.push(item); - } - - return; - } - - this.children.push(item); - - var len = this.children.length; - - if(this.depth < this.maxDepth && len > this.maxChildren){ - this.subdivide(); - - for(var i=0; i