UNPKG

@cocos/cannon

Version:

A lightweight 3D physics engine written in JavaScript.

1,676 lines (1,460 loc) 454 kB
// Tue, 20 Jul 2021 10:46:34 GMT /* * Copyright (c) 2015 cannon.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){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.CANNON=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<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ module.exports={ "name": "@cocos/cannon", "version": "1.2.8", "description": "A lightweight 3D physics engine written in JavaScript.", "homepage": "https://github.com/cocos-creator/cannon.js", "author": "Stefan Hedman <schteppe@gmail.com> (http://steffe.se), JayceLai", "keywords": [ "cannon.js", "cocos", "creator", "physics", "engine", "3d" ], "scripts": { "build":"grunt && npm run preprocess && grunt addLicense && grunt addDate", "preprocess":"node node_modules/uglify-js/bin/uglifyjs build/cannon.js -o build/cannon.min.js -c -m", "postpublish": "cnpm sync @cocos/cannon" }, "main": "./build/cannon.js", "engines": { "node": "*" }, "repository": { "type": "git", "url": "https://github.com/cocos-creator/cannon.js.git" }, "bugs": { "url": "https://github.com/cocos-creator/cannon.js/issues" }, "licenses": [ { "type": "MIT" } ], "devDependencies": { "jshint": "latest", "uglify-js": "latest", "nodeunit": "^0.9.0", "grunt": "~0.4.0", "grunt-contrib-jshint": "~0.1.1", "grunt-contrib-nodeunit": "^0.4.1", "grunt-contrib-concat": "~0.1.3", "grunt-contrib-uglify": "^0.5.1", "grunt-browserify": "^2.1.4", "grunt-contrib-yuidoc": "^0.5.2", "browserify": "*" }, "dependencies": {} } },{}],2:[function(_dereq_,module,exports){ // Export classes module.exports = { version : _dereq_('../package.json').version, AABB : _dereq_('./collision/AABB'), ArrayCollisionMatrix : _dereq_('./collision/ArrayCollisionMatrix'), Body : _dereq_('./objects/Body'), Box : _dereq_('./shapes/Box'), Broadphase : _dereq_('./collision/Broadphase'), Constraint : _dereq_('./constraints/Constraint'), ContactEquation : _dereq_('./equations/ContactEquation'), Narrowphase : _dereq_('./world/Narrowphase'), ConeTwistConstraint : _dereq_('./constraints/ConeTwistConstraint'), ContactMaterial : _dereq_('./material/ContactMaterial'), ConvexPolyhedron : _dereq_('./shapes/ConvexPolyhedron'), Cylinder : _dereq_('./shapes/Cylinder'), DistanceConstraint : _dereq_('./constraints/DistanceConstraint'), Equation : _dereq_('./equations/Equation'), EventTarget : _dereq_('./utils/EventTarget'), FrictionEquation : _dereq_('./equations/FrictionEquation'), GSSolver : _dereq_('./solver/GSSolver'), GridBroadphase : _dereq_('./collision/GridBroadphase'), Heightfield : _dereq_('./shapes/Heightfield'), HingeConstraint : _dereq_('./constraints/HingeConstraint'), LockConstraint : _dereq_('./constraints/LockConstraint'), Mat3 : _dereq_('./math/Mat3'), Material : _dereq_('./material/Material'), NaiveBroadphase : _dereq_('./collision/NaiveBroadphase'), ObjectCollisionMatrix : _dereq_('./collision/ObjectCollisionMatrix'), Pool : _dereq_('./utils/Pool'), Particle : _dereq_('./shapes/Particle'), Plane : _dereq_('./shapes/Plane'), PointToPointConstraint : _dereq_('./constraints/PointToPointConstraint'), Quaternion : _dereq_('./math/Quaternion'), Ray : _dereq_('./collision/Ray'), RaycastVehicle : _dereq_('./objects/RaycastVehicle'), RaycastResult : _dereq_('./collision/RaycastResult'), RigidVehicle : _dereq_('./objects/RigidVehicle'), RotationalEquation : _dereq_('./equations/RotationalEquation'), RotationalMotorEquation : _dereq_('./equations/RotationalMotorEquation'), SAPBroadphase : _dereq_('./collision/SAPBroadphase'), SPHSystem : _dereq_('./objects/SPHSystem'), Shape : _dereq_('./shapes/Shape'), Solver : _dereq_('./solver/Solver'), Sphere : _dereq_('./shapes/Sphere'), SplitSolver : _dereq_('./solver/SplitSolver'), Spring : _dereq_('./objects/Spring'), Transform : _dereq_('./math/Transform'), Trimesh : _dereq_('./shapes/Trimesh'), Vec3 : _dereq_('./math/Vec3'), Vec3Pool : _dereq_('./utils/Vec3Pool'), World : _dereq_('./world/World'), Octree : _dereq_('./utils/Octree'), }; },{"../package.json":1,"./collision/AABB":3,"./collision/ArrayCollisionMatrix":4,"./collision/Broadphase":5,"./collision/GridBroadphase":6,"./collision/NaiveBroadphase":7,"./collision/ObjectCollisionMatrix":8,"./collision/Ray":10,"./collision/RaycastResult":11,"./collision/SAPBroadphase":12,"./constraints/ConeTwistConstraint":13,"./constraints/Constraint":14,"./constraints/DistanceConstraint":15,"./constraints/HingeConstraint":16,"./constraints/LockConstraint":17,"./constraints/PointToPointConstraint":18,"./equations/ContactEquation":20,"./equations/Equation":21,"./equations/FrictionEquation":22,"./equations/RotationalEquation":23,"./equations/RotationalMotorEquation":24,"./material/ContactMaterial":25,"./material/Material":26,"./math/Mat3":28,"./math/Quaternion":29,"./math/Transform":30,"./math/Vec3":31,"./objects/Body":32,"./objects/RaycastVehicle":33,"./objects/RigidVehicle":34,"./objects/SPHSystem":35,"./objects/Spring":36,"./shapes/Box":38,"./shapes/ConvexPolyhedron":39,"./shapes/Cylinder":40,"./shapes/Heightfield":41,"./shapes/Particle":42,"./shapes/Plane":43,"./shapes/Shape":44,"./shapes/Sphere":45,"./shapes/Trimesh":46,"./solver/GSSolver":47,"./solver/Solver":48,"./solver/SplitSolver":49,"./utils/EventTarget":50,"./utils/Octree":51,"./utils/Pool":52,"./utils/Vec3Pool":55,"./world/Narrowphase":56,"./world/World":57}],3:[function(_dereq_,module,exports){ var Vec3 = _dereq_('../math/Vec3'); var Utils = _dereq_('../utils/Utils'); module.exports = AABB; /** * Axis aligned bounding box class. * @class AABB * @constructor * @param {Object} [options] * @param {Vec3} [options.upperBound] * @param {Vec3} [options.lowerBound] */ function AABB(options){ options = options || {}; /** * The lower bound of the bounding box. * @property lowerBound * @type {Vec3} */ this.lowerBound = new Vec3(); if(options.lowerBound){ this.lowerBound.copy(options.lowerBound); } /** * The upper bound of the bounding box. * @property upperBound * @type {Vec3} */ this.upperBound = new Vec3(); if(options.upperBound){ this.upperBound.copy(options.upperBound); } } var tmp = new Vec3(); /** * Set the AABB bounds from a set of points. * @method setFromPoints * @param {Array} points An array of Vec3's. * @param {Vec3} position * @param {Quaternion} quaternion * @param {number} skinSize * @return {AABB} The self object */ AABB.prototype.setFromPoints = function(points, position, quaternion, skinSize){ var l = this.lowerBound, u = this.upperBound, q = quaternion; // Set to the first point l.copy(points[0]); if(q){ q.vmult(l, l); } u.copy(l); for(var i = 1; i<points.length; i++){ var p = points[i]; if(q){ q.vmult(p, tmp); p = tmp; } if(p.x > u.x){ u.x = p.x; } if(p.x < l.x){ l.x = p.x; } if(p.y > u.y){ u.y = p.y; } if(p.y < l.y){ l.y = p.y; } if(p.z > u.z){ u.z = p.z; } if(p.z < l.z){ l.z = p.z; } } // Add offset if (position) { position.vadd(l, l); position.vadd(u, u); } if(skinSize){ l.x -= skinSize; l.y -= skinSize; l.z -= skinSize; u.x += skinSize; u.y += skinSize; u.z += skinSize; } return this; }; /** * Copy bounds from an AABB to this AABB * @method copy * @param {AABB} aabb Source to copy from * @return {AABB} The this object, for chainability */ AABB.prototype.copy = function(aabb){ this.lowerBound.copy(aabb.lowerBound); this.upperBound.copy(aabb.upperBound); return this; }; /** * Clone an AABB * @method clone */ AABB.prototype.clone = function(){ return new AABB().copy(this); }; /** * Extend this AABB so that it covers the given AABB too. * @method extend * @param {AABB} aabb */ AABB.prototype.extend = function(aabb){ this.lowerBound.x = Math.min(this.lowerBound.x, aabb.lowerBound.x); this.upperBound.x = Math.max(this.upperBound.x, aabb.upperBound.x); this.lowerBound.y = Math.min(this.lowerBound.y, aabb.lowerBound.y); this.upperBound.y = Math.max(this.upperBound.y, aabb.upperBound.y); this.lowerBound.z = Math.min(this.lowerBound.z, aabb.lowerBound.z); this.upperBound.z = Math.max(this.upperBound.z, aabb.upperBound.z); }; /** * 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 var overlapsX = ((l2.x <= u1.x && u1.x <= u2.x) || (l1.x <= u2.x && u2.x <= u1.x)); var overlapsY = ((l2.y <= u1.y && u1.y <= u2.y) || (l1.y <= u2.y && u2.y <= u1.y)); var overlapsZ = ((l2.z <= u1.z && u1.z <= u2.z) || (l1.z <= u2.z && u2.z <= u1.z)); return overlapsX && overlapsY && overlapsZ; }; // Mostly for debugging AABB.prototype.volume = function(){ var l = this.lowerBound, u = this.upperBound; return (u.x - l.x) * (u.y - l.y) * (u.z - l.z); }; /** * Returns true if the given AABB is fully contained in this AABB. * @method contains * @param {AABB} aabb * @return {Boolean} */ AABB.prototype.contains = function(aabb){ var l1 = this.lowerBound, u1 = this.upperBound, l2 = aabb.lowerBound, u2 = aabb.upperBound; // l2 u2 // |---------| // |---------------| // l1 u1 return ( (l1.x <= l2.x && u1.x >= u2.x) && (l1.y <= l2.y && u1.y >= u2.y) && (l1.z <= l2.z && u1.z >= u2.z) ); }; /** * @method getCorners * @param {Vec3} a * @param {Vec3} b * @param {Vec3} c * @param {Vec3} d * @param {Vec3} e * @param {Vec3} f * @param {Vec3} g * @param {Vec3} h */ AABB.prototype.getCorners = function(a, b, c, d, e, f, g, h){ var l = this.lowerBound, u = this.upperBound; a.copy(l); b.set( u.x, l.y, l.z ); c.set( u.x, u.y, l.z ); d.set( l.x, u.y, u.z ); e.set( u.x, l.y, u.z ); f.set( l.x, u.y, l.z ); g.set( l.x, l.y, u.z ); h.copy(u); }; var transformIntoFrame_corners = [ new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3() ]; /** * Get the representation of an AABB in another frame. * @method toLocalFrame * @param {Transform} frame * @param {AABB} target * @return {AABB} The "target" AABB object. */ AABB.prototype.toLocalFrame = function(frame, target){ var corners = transformIntoFrame_corners; var a = corners[0]; var b = corners[1]; var c = corners[2]; var d = corners[3]; var e = corners[4]; var f = corners[5]; var g = corners[6]; var h = corners[7]; // Get corners in current frame this.getCorners(a, b, c, d, e, f, g, h); // Transform them to new local frame for(var i=0; i !== 8; i++){ var corner = corners[i]; frame.pointToLocal(corner, corner); } return target.setFromPoints(corners); }; /** * Get the representation of an AABB in the global frame. * @method toWorldFrame * @param {Transform} frame * @param {AABB} target * @return {AABB} The "target" AABB object. */ AABB.prototype.toWorldFrame = function(frame, target){ var corners = transformIntoFrame_corners; var a = corners[0]; var b = corners[1]; var c = corners[2]; var d = corners[3]; var e = corners[4]; var f = corners[5]; var g = corners[6]; var h = corners[7]; // Get corners in current frame this.getCorners(a, b, c, d, e, f, g, h); // Transform them to new local frame for(var i=0; i !== 8; i++){ var corner = corners[i]; frame.pointToWorld(corner, corner); } return target.setFromPoints(corners); }; /** * Check if the AABB is hit by a ray. * @param {Ray} ray * @return {number} */ AABB.prototype.overlapsRay = function(ray){ var t = 0; // ray.direction is unit direction vector of ray var dirFracX = 1 / ray._direction.x; var dirFracY = 1 / ray._direction.y; var dirFracZ = 1 / ray._direction.z; // this.lowerBound is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner var t1 = (this.lowerBound.x - ray.from.x) * dirFracX; var t2 = (this.upperBound.x - ray.from.x) * dirFracX; var t3 = (this.lowerBound.y - ray.from.y) * dirFracY; var t4 = (this.upperBound.y - ray.from.y) * dirFracY; var t5 = (this.lowerBound.z - ray.from.z) * dirFracZ; var t6 = (this.upperBound.z - ray.from.z) * dirFracZ; // var tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4))); // var tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4))); var tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6)); var tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6)); // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us if (tmax < 0){ //t = tmax; return false; } // if tmin > tmax, ray doesn't intersect AABB if (tmin > tmax){ //t = tmax; return false; } return true; }; },{"../math/Vec3":31,"../utils/Utils":54}],4:[function(_dereq_,module,exports){ module.exports = ArrayCollisionMatrix; /** * Collision "matrix". It's actually a triangular-shaped array of whether two bodies are touching this step, for reference next step * @class ArrayCollisionMatrix * @constructor */ function ArrayCollisionMatrix() { /** * The matrix storage * @property matrix * @type {Array} */ this.matrix = []; } /** * Get an element * @method get * @param {Number} i * @param {Number} j * @return {Number} */ ArrayCollisionMatrix.prototype.get = function(i, j) { i = i.index; j = j.index; if (j > i) { var temp = j; j = i; i = temp; } return this.matrix[(i*(i + 1)>>1) + j-1]; }; /** * Set an element * @method set * @param {Number} i * @param {Number} j * @param {Number} value */ ArrayCollisionMatrix.prototype.set = function(i, j, value) { i = i.index; j = j.index; if (j > i) { var temp = j; j = i; i = temp; } this.matrix[(i*(i + 1)>>1) + j-1] = value ? 1 : 0; }; /** * Sets all elements to zero * @method reset */ ArrayCollisionMatrix.prototype.reset = function() { for (var i=0, l=this.matrix.length; i!==l; i++) { this.matrix[i]=0; } }; /** * Sets the max number of objects * @method setNumObjects * @param {Number} n */ ArrayCollisionMatrix.prototype.setNumObjects = function(n) { this.matrix.length = n*(n-1)>>1; }; },{}],5:[function(_dereq_,module,exports){ var Body = _dereq_('../objects/Body'); var Vec3 = _dereq_('../math/Vec3'); var Quaternion = _dereq_('../math/Quaternion'); var Shape = _dereq_('../shapes/Shape'); var Plane = _dereq_('../shapes/Plane'); module.exports = Broadphase; /** * Base class for broadphase implementations * @class Broadphase * @constructor * @author schteppe */ function Broadphase(){ /** * The world to search for collisions in. * @property world * @type {World} */ this.world = null; /** * If set to true, the broadphase uses bounding boxes for intersection test, else it uses bounding spheres. * @property useBoundingBoxes * @type {Boolean} */ this.useBoundingBoxes = false; /** * Set to true if the objects in the world moved. * @property {Boolean} dirty */ this.dirty = true; } /** * Get the collision pairs from the world * @method collisionPairs * @param {World} world The world to search in * @param {Array} p1 Empty array to be filled with body objects * @param {Array} p2 Empty array to be filled with body objects */ Broadphase.prototype.collisionPairs = function(world,p1,p2){ throw new Error("collisionPairs not implemented for this BroadPhase class!"); }; /** * Check if a body pair needs to be intersection tested at all. * @method needBroadphaseCollision * @param {Body} bodyA * @param {Body} bodyB * @return {bool} */ Broadphase.prototype.needBroadphaseCollision = function(bodyA,bodyB){ // Check collision filter masks if( (bodyA.collisionFilterGroup & bodyB.collisionFilterMask)===0 || (bodyB.collisionFilterGroup & bodyA.collisionFilterMask)===0){ return false; } var isSA = (bodyA.type & Body.STATIC)!==0; var isSB = (bodyB.type & Body.STATIC)!==0; // s - s if(isSA && isSB) return false; // Check has trigger if(bodyA.hasTrigger || bodyB.hasTrigger){ return true; } else { // Check sleep state if((bodyA.sleepState === Body.SLEEPING) && (bodyB.sleepState === Body.SLEEPING)) { // Both bodies are static or sleeping. Skip. return false; } } return true; }; /** * Check if the bounding volumes of two bodies intersect. * @method intersectionTest * @param {Body} bodyA * @param {Body} bodyB * @param {array} pairs1 * @param {array} pairs2 */ Broadphase.prototype.intersectionTest = function(bodyA, bodyB, pairs1, pairs2){ if(this.useBoundingBoxes){ this.doBoundingBoxBroadphase(bodyA,bodyB,pairs1,pairs2); } else { this.doBoundingSphereBroadphase(bodyA,bodyB,pairs1,pairs2); } }; /** * Check if the bounding spheres of two bodies are intersecting. * @method doBoundingSphereBroadphase * @param {Body} bodyA * @param {Body} bodyB * @param {Array} pairs1 bodyA is appended to this array if intersection * @param {Array} pairs2 bodyB is appended to this array if intersection */ var Broadphase_collisionPairs_r = new Vec3(), // Temp objects Broadphase_collisionPairs_normal = new Vec3(), Broadphase_collisionPairs_quat = new Quaternion(), Broadphase_collisionPairs_relpos = new Vec3(); Broadphase.prototype.doBoundingSphereBroadphase = function(bodyA,bodyB,pairs1,pairs2){ var r = Broadphase_collisionPairs_r; bodyB.position.vsub(bodyA.position,r); var boundingRadiusSum2 = Math.pow(bodyA.boundingRadius + bodyB.boundingRadius, 2); var norm2 = r.norm2(); if(norm2 < boundingRadiusSum2){ pairs1.push(bodyA); pairs2.push(bodyB); } }; /** * Check if the bounding boxes of two bodies are intersecting. * @method doBoundingBoxBroadphase * @param {Body} bodyA * @param {Body} bodyB * @param {Array} pairs1 * @param {Array} pairs2 */ Broadphase.prototype.doBoundingBoxBroadphase = function(bodyA,bodyB,pairs1,pairs2){ if(bodyA.aabbNeedsUpdate){ bodyA.computeAABB(); } if(bodyB.aabbNeedsUpdate){ bodyB.computeAABB(); } // Check AABB / AABB if(bodyA.aabb.overlaps(bodyB.aabb)){ pairs1.push(bodyA); pairs2.push(bodyB); } }; /** * Removes duplicate pairs from the pair arrays. * @method makePairsUnique * @param {Array} pairs1 * @param {Array} pairs2 */ var Broadphase_makePairsUnique_temp = { keys:[] }, Broadphase_makePairsUnique_p1 = [], Broadphase_makePairsUnique_p2 = []; Broadphase.prototype.makePairsUnique = function(pairs1,pairs2){ var t = Broadphase_makePairsUnique_temp, p1 = Broadphase_makePairsUnique_p1, p2 = Broadphase_makePairsUnique_p2, N = pairs1.length; for(var i=0; i!==N; i++){ p1[i] = pairs1[i]; p2[i] = pairs2[i]; } pairs1.length = 0; pairs2.length = 0; for(var i=0; i!==N; i++){ var id1 = p1[i].id, id2 = p2[i].id; var key = id1 < id2 ? id1+","+id2 : id2+","+id1; t[key] = i; t.keys.push(key); } for(var i=0; i!==t.keys.length; i++){ var key = t.keys.pop(), pairIndex = t[key]; pairs1.push(p1[pairIndex]); pairs2.push(p2[pairIndex]); delete t[key]; } }; /** * To be implemented by subcasses * @method setWorld * @param {World} world */ Broadphase.prototype.setWorld = function(world){ }; /** * Check if the bounding spheres of two bodies overlap. * @method boundingSphereCheck * @param {Body} bodyA * @param {Body} bodyB * @return {boolean} */ var bsc_dist = new Vec3(); Broadphase.boundingSphereCheck = function(bodyA,bodyB){ var dist = bsc_dist; bodyA.position.vsub(bodyB.position,dist); return Math.pow(bodyA.shape.boundingSphereRadius + bodyB.shape.boundingSphereRadius,2) > dist.norm2(); }; /** * Returns all the bodies within the AABB. * @method aabbQuery * @param {World} world * @param {AABB} aabb * @param {array} result An array to store resulting bodies in. * @return {array} */ Broadphase.prototype.aabbQuery = function(world, aabb, result){ console.warn('.aabbQuery is not implemented in this Broadphase subclass.'); return []; }; },{"../math/Quaternion":29,"../math/Vec3":31,"../objects/Body":32,"../shapes/Plane":43,"../shapes/Shape":44}],6:[function(_dereq_,module,exports){ module.exports = GridBroadphase; var Broadphase = _dereq_('./Broadphase'); var Vec3 = _dereq_('../math/Vec3'); var Shape = _dereq_('../shapes/Shape'); /** * Axis aligned uniform grid broadphase. * @class GridBroadphase * @constructor * @extends Broadphase * @todo Needs support for more than just planes and spheres. * @param {Vec3} aabbMin * @param {Vec3} aabbMax * @param {Number} nx Number of boxes along x * @param {Number} ny Number of boxes along y * @param {Number} nz Number of boxes along z */ function GridBroadphase(aabbMin,aabbMax,nx,ny,nz){ Broadphase.apply(this); this.nx = nx || 10; this.ny = ny || 10; this.nz = nz || 10; this.aabbMin = aabbMin || new Vec3(100,100,100); this.aabbMax = aabbMax || new Vec3(-100,-100,-100); var nbins = this.nx * this.ny * this.nz; if (nbins <= 0) { throw "GridBroadphase: Each dimension's n must be >0"; } this.bins = []; this.binLengths = []; //Rather than continually resizing arrays (thrashing the memory), just record length and allow them to grow this.bins.length = nbins; this.binLengths.length = nbins; for (var i=0;i<nbins;i++) { this.bins[i]=[]; this.binLengths[i]=0; } } GridBroadphase.prototype = new Broadphase(); GridBroadphase.prototype.constructor = GridBroadphase; /** * Get all the collision pairs in the physics world * @method collisionPairs * @param {World} world * @param {Array} pairs1 * @param {Array} pairs2 */ var GridBroadphase_collisionPairs_d = new Vec3(); var GridBroadphase_collisionPairs_binPos = new Vec3(); GridBroadphase.prototype.collisionPairs = function(world,pairs1,pairs2){ var N = world.numObjects(), bodies = world.bodies; var max = this.aabbMax, min = this.aabbMin, nx = this.nx, ny = this.ny, nz = this.nz; var xstep = ny*nz; var ystep = nz; var zstep = 1; var xmax = max.x, ymax = max.y, zmax = max.z, xmin = min.x, ymin = min.y, zmin = min.z; var xmult = nx / (xmax-xmin), ymult = ny / (ymax-ymin), zmult = nz / (zmax-zmin); var binsizeX = (xmax - xmin) / nx, binsizeY = (ymax - ymin) / ny, binsizeZ = (zmax - zmin) / nz; var binRadius = Math.sqrt(binsizeX*binsizeX + binsizeY*binsizeY + binsizeZ*binsizeZ) * 0.5; var types = Shape.types; var SPHERE = types.SPHERE, PLANE = types.PLANE, BOX = types.BOX, COMPOUND = types.COMPOUND, CONVEXPOLYHEDRON = types.CONVEXPOLYHEDRON; var bins=this.bins, binLengths=this.binLengths, Nbins=this.bins.length; // Reset bins for(var i=0; i!==Nbins; i++){ binLengths[i] = 0; } var ceil = Math.ceil; var min = Math.min; var max = Math.max; function addBoxToBins(x0,y0,z0,x1,y1,z1,bi) { var xoff0 = ((x0 - xmin) * xmult)|0, yoff0 = ((y0 - ymin) * ymult)|0, zoff0 = ((z0 - zmin) * zmult)|0, xoff1 = ceil((x1 - xmin) * xmult), yoff1 = ceil((y1 - ymin) * ymult), zoff1 = ceil((z1 - zmin) * zmult); if (xoff0 < 0) { xoff0 = 0; } else if (xoff0 >= nx) { xoff0 = nx - 1; } if (yoff0 < 0) { yoff0 = 0; } else if (yoff0 >= ny) { yoff0 = ny - 1; } if (zoff0 < 0) { zoff0 = 0; } else if (zoff0 >= nz) { zoff0 = nz - 1; } if (xoff1 < 0) { xoff1 = 0; } else if (xoff1 >= nx) { xoff1 = nx - 1; } if (yoff1 < 0) { yoff1 = 0; } else if (yoff1 >= ny) { yoff1 = ny - 1; } if (zoff1 < 0) { zoff1 = 0; } else if (zoff1 >= nz) { zoff1 = nz - 1; } xoff0 *= xstep; yoff0 *= ystep; zoff0 *= zstep; xoff1 *= xstep; yoff1 *= ystep; zoff1 *= zstep; for (var xoff = xoff0; xoff <= xoff1; xoff += xstep) { for (var yoff = yoff0; yoff <= yoff1; yoff += ystep) { for (var zoff = zoff0; zoff <= zoff1; zoff += zstep) { var idx = xoff+yoff+zoff; bins[idx][binLengths[idx]++] = bi; } } } } // Put all bodies into the bins for(var i=0; i!==N; i++){ var bi = bodies[i]; var si = bi.shape; switch(si.type){ case SPHERE: // Put in bin // check if overlap with other bins var x = bi.position.x, y = bi.position.y, z = bi.position.z; var r = si.radius; addBoxToBins(x-r, y-r, z-r, x+r, y+r, z+r, bi); break; case PLANE: if(si.worldNormalNeedsUpdate){ si.computeWorldNormal(bi.quaternion); } var planeNormal = si.worldNormal; //Relative position from origin of plane object to the first bin //Incremented as we iterate through the bins var xreset = xmin + binsizeX*0.5 - bi.position.x, yreset = ymin + binsizeY*0.5 - bi.position.y, zreset = zmin + binsizeZ*0.5 - bi.position.z; var d = GridBroadphase_collisionPairs_d; d.set(xreset, yreset, zreset); for (var xi = 0, xoff = 0; xi !== nx; xi++, xoff += xstep, d.y = yreset, d.x += binsizeX) { for (var yi = 0, yoff = 0; yi !== ny; yi++, yoff += ystep, d.z = zreset, d.y += binsizeY) { for (var zi = 0, zoff = 0; zi !== nz; zi++, zoff += zstep, d.z += binsizeZ) { if (d.dot(planeNormal) < binRadius) { var idx = xoff + yoff + zoff; bins[idx][binLengths[idx]++] = bi; } } } } break; default: if (bi.aabbNeedsUpdate) { bi.computeAABB(); } addBoxToBins( bi.aabb.lowerBound.x, bi.aabb.lowerBound.y, bi.aabb.lowerBound.z, bi.aabb.upperBound.x, bi.aabb.upperBound.y, bi.aabb.upperBound.z, bi); break; } } // Check each bin for(var i=0; i!==Nbins; i++){ var binLength = binLengths[i]; //Skip bins with no potential collisions if (binLength > 1) { var bin = bins[i]; // Do N^2 broadphase inside for(var xi=0; xi!==binLength; xi++){ var bi = bin[xi]; for(var yi=0; yi!==xi; yi++){ var bj = bin[yi]; if(this.needBroadphaseCollision(bi,bj)){ this.intersectionTest(bi,bj,pairs1,pairs2); } } } } } // for (var zi = 0, zoff=0; zi < nz; zi++, zoff+= zstep) { // console.log("layer "+zi); // for (var yi = 0, yoff=0; yi < ny; yi++, yoff += ystep) { // var row = ''; // for (var xi = 0, xoff=0; xi < nx; xi++, xoff += xstep) { // var idx = xoff + yoff + zoff; // row += ' ' + binLengths[idx]; // } // console.log(row); // } // } this.makePairsUnique(pairs1,pairs2); }; },{"../math/Vec3":31,"../shapes/Shape":44,"./Broadphase":5}],7:[function(_dereq_,module,exports){ module.exports = NaiveBroadphase; var Broadphase = _dereq_('./Broadphase'); var AABB = _dereq_('./AABB'); /** * Naive broadphase implementation, used in lack of better ones. * @class NaiveBroadphase * @constructor * @description The naive broadphase looks at all possible pairs without restriction, therefore it has complexity N^2 (which is bad) * @extends Broadphase */ function NaiveBroadphase(){ Broadphase.apply(this); } NaiveBroadphase.prototype = new Broadphase(); NaiveBroadphase.prototype.constructor = NaiveBroadphase; /** * Get all the collision pairs in the physics world * @method collisionPairs * @param {World} world * @param {Array} pairs1 * @param {Array} pairs2 */ NaiveBroadphase.prototype.collisionPairs = function(world,pairs1,pairs2){ var bodies = world.bodies, n = bodies.length, i,j,bi,bj; // Naive N^2 ftw! for(i=0; i!==n; i++){ for(j=0; j!==i; j++){ bi = bodies[i]; bj = bodies[j]; if(!this.needBroadphaseCollision(bi,bj)){ continue; } this.intersectionTest(bi,bj,pairs1,pairs2); } } }; var tmpAABB = new AABB(); /** * Returns all the bodies within an AABB. * @method aabbQuery * @param {World} world * @param {AABB} aabb * @param {array} result An array to store resulting bodies in. * @return {array} */ NaiveBroadphase.prototype.aabbQuery = function(world, aabb, result){ result = result || []; for(var i = 0; i < world.bodies.length; i++){ var b = world.bodies[i]; if(b.aabbNeedsUpdate){ b.computeAABB(); } // Ugly hack until Body gets aabb if(b.aabb.overlaps(aabb)){ result.push(b); } } return result; }; },{"./AABB":3,"./Broadphase":5}],8:[function(_dereq_,module,exports){ module.exports = ObjectCollisionMatrix; /** * Records what objects are colliding with each other * @class ObjectCollisionMatrix * @constructor */ function ObjectCollisionMatrix() { /** * The matrix storage * @property matrix * @type {Object} */ this.matrix = {}; } /** * @method get * @param {Number} i * @param {Number} j * @return {Number} */ ObjectCollisionMatrix.prototype.get = function(i, j) { i = i.id; j = j.id; if (j > i) { var temp = j; j = i; i = temp; } return i+'-'+j in this.matrix; }; /** * @method set * @param {Number} i * @param {Number} j * @param {Number} value */ ObjectCollisionMatrix.prototype.set = function(i, j, value) { i = i.id; j = j.id; if (j > i) { var temp = j; j = i; i = temp; } if (value) { this.matrix[i+'-'+j] = true; } else { delete this.matrix[i+'-'+j]; } }; /** * Empty the matrix * @method reset */ ObjectCollisionMatrix.prototype.reset = function() { this.matrix = {}; }; /** * Set max number of objects * @method setNumObjects * @param {Number} n */ ObjectCollisionMatrix.prototype.setNumObjects = function(n) { }; },{}],9:[function(_dereq_,module,exports){ module.exports = OverlapKeeper; /** * @class OverlapKeeper * @constructor */ function OverlapKeeper() { this.current = []; this.previous = []; } OverlapKeeper.prototype.getKey = function(i, j) { if (j < i) { var temp = j; j = i; i = temp; } return (i << 16) | j; }; /** * @method set * @param {Number} i * @param {Number} j */ OverlapKeeper.prototype.set = function(i, j) { // Insertion sort. This way the diff will have linear complexity. var key = this.getKey(i, j); var current = this.current; var index = 0; while(key > current[index]){ index++; } if(key === current[index]){ return; // Pair was already added } for(var j=current.length-1; j>=index; j--){ current[j + 1] = current[j]; } current[index] = key; }; /** * @method tick */ OverlapKeeper.prototype.tick = function() { var tmp = this.current; this.current = this.previous; this.previous = tmp; this.current.length = 0; }; function unpackAndPush(array, key){ array.push((key & 0xFFFF0000) >> 16, key & 0x0000FFFF); } /** * @method getDiff * @param {array} additions * @param {array} removals */ OverlapKeeper.prototype.getDiff = function(additions, removals) { var a = this.current; var b = this.previous; var al = a.length; var bl = b.length; var j=0; for (var i = 0; i < al; i++) { var found = false; var keyA = a[i]; while(keyA > b[j]){ j++; } found = keyA === b[j]; if(!found){ unpackAndPush(additions, keyA); } } j = 0; for (var i = 0; i < bl; i++) { var found = false; var keyB = b[i]; while(keyB > a[j]){ j++; } found = a[j] === keyB; if(!found){ unpackAndPush(removals, keyB); } } }; },{}],10:[function(_dereq_,module,exports){ module.exports = Ray; var Vec3 = _dereq_('../math/Vec3'); var Quaternion = _dereq_('../math/Quaternion'); var Transform = _dereq_('../math/Transform'); var ConvexPolyhedron = _dereq_('../shapes/ConvexPolyhedron'); var Box = _dereq_('../shapes/Box'); var RaycastResult = _dereq_('../collision/RaycastResult'); var Shape = _dereq_('../shapes/Shape'); var AABB = _dereq_('../collision/AABB'); /** * A line in 3D space that intersects bodies and return points. * @class Ray * @constructor * @param {Vec3} from * @param {Vec3} to */ function Ray(from, to){ /** * @property {Vec3} from */ this.from = from ? from.clone() : new Vec3(); /** * @property {Vec3} to */ this.to = to ? to.clone() : new Vec3(); /** * @private * @property {Vec3} _direction */ this._direction = new Vec3(); /** * The precision of the ray. Used when checking parallelity etc. * @property {Number} precision */ this.precision = 0.0001; /** * Set to true if you want the Ray to take .collisionResponse flags into account on bodies and shapes. * @property {Boolean} checkCollisionResponse */ this.checkCollisionResponse = true; /** * If set to true, the ray skips any hits with normal.dot(rayDirection) < 0. * @property {Boolean} skipBackfaces */ this.skipBackfaces = false; /** * @property {number} collisionFilterMask * @default -1 */ this.collisionFilterMask = -1; /** * @property {number} collisionFilterGroup * @default -1 */ this.collisionFilterGroup = -1; /** * The intersection mode. Should be Ray.ANY, Ray.ALL or Ray.CLOSEST. * @property {number} mode */ this.mode = Ray.ANY; /** * Current result object. * @property {RaycastResult} result */ this.result = new RaycastResult(); /** * Will be set to true during intersectWorld() if the ray hit anything. * @property {Boolean} hasHit */ this.hasHit = false; /** * Current, user-provided result callback. Will be used if mode is Ray.ALL. * @property {Function} callback */ this.callback = function(result){}; } Ray.prototype.constructor = Ray; Ray.CLOSEST = 1; Ray.ANY = 2; Ray.ALL = 4; var tmpAABB = new AABB(); var tmpArray = []; /** * Do itersection against all bodies in the given World. * @method intersectWorld * @param {World} world * @param {object} options * @return {Boolean} True if the ray hit anything, otherwise false. */ Ray.prototype.intersectWorld = function (world, options) { this.mode = options.mode || Ray.ANY; this.result = options.result || new RaycastResult(); this.skipBackfaces = !!options.skipBackfaces; this.checkCollisionResponse = !!options.checkCollisionResponse; this.collisionFilterMask = typeof(options.collisionFilterMask) !== 'undefined' ? options.collisionFilterMask : -1; this.collisionFilterGroup = typeof(options.collisionFilterGroup) !== 'undefined' ? options.collisionFilterGroup : -1; if(options.from){ this.from.copy(options.from); } if(options.to){ this.to.copy(options.to); } this.callback = options.callback || function(){}; this.hasHit = false; this.result.reset(); this._updateDirection(); this.getAABB(tmpAABB); tmpArray.length = 0; world.broadphase.aabbQuery(world, tmpAABB, tmpArray); this.intersectBodies(tmpArray); return this.hasHit; }; var v1 = new Vec3(), v2 = new Vec3(); /* * As per "Barycentric Technique" as named here http://www.blackpawn.com/texts/pointinpoly/default.html But without the division */ Ray.pointInTriangle = pointInTriangle; function pointInTriangle(p, a, b, c) { c.vsub(a,v0); b.vsub(a,v1); p.vsub(a,v2); var dot00 = v0.dot( v0 ); var dot01 = v0.dot( v1 ); var dot02 = v0.dot( v2 ); var dot11 = v1.dot( v1 ); var dot12 = v1.dot( v2 ); var u,v; return ( (u = dot11 * dot02 - dot01 * dot12) >= 0 ) && ( (v = dot00 * dot12 - dot01 * dot02) >= 0 ) && ( u + v < ( dot00 * dot11 - dot01 * dot01 ) ); } /** * Shoot a ray at a body, get back information about the hit. * @method intersectBody * @private * @param {Body} body * @param {RaycastResult} [result] Deprecated - set the result property of the Ray instead. */ var intersectBody_xi = new Vec3(); var intersectBody_qi = new Quaternion(); Ray.prototype.intersectBody = function (body, result) { if(result){ this.result = result; this._updateDirection(); } var checkCollisionResponse = this.checkCollisionResponse; if(checkCollisionResponse && !body.collisionResponse){ return; } if (!Ray.perBodyFilter(this, body)) return; var xi = intersectBody_xi; var qi = intersectBody_qi; for (var i = 0, N = body.shapes.length; i < N; i++) { var shape = body.shapes[i]; if (!Ray.perShapeFilter(this, shape)) continue; body.quaternion.mult(body.shapeOrientations[i], qi); body.quaternion.vmult(body.shapeOffsets[i], xi); xi.vadd(body.position, xi); this.intersectShape( shape, qi, xi, body ); if(this.result._shouldStop){ break; } } }; /** * @method intersectBodies * @param {Array} bodies An array of Body objects. * @param {RaycastResult} [result] Deprecated */ Ray.prototype.intersectBodies = function (bodies, result) { if(result){ this.result = result; this._updateDirection(); } for ( var i = 0, l = bodies.length; !this.result._shouldStop && i < l; i ++ ) { this.intersectBody(bodies[i]); } }; /** * Updates the _direction vector. * @private * @method _updateDirection */ Ray.prototype._updateDirection = function(){ this.to.vsub(this.from, this._direction); this._direction.normalize(); }; /** * @method intersectShape * @private * @param {Shape} shape * @param {Quaternion} quat * @param {Vec3} position * @param {Body} body */ Ray.prototype.intersectShape = function(shape, quat, position, body){ var from = this.from; // Checking boundingSphere var distance = distanceFromIntersection(from, this._direction, position); if ( distance > shape.boundingSphereRadius ) { return; } var intersectMethod = this[shape.type]; if(intersectMethod){ intersectMethod.call(this, shape, quat, position, body, shape); } }; var vector = new Vec3(); var normal = new Vec3(); var intersectPoint = new Vec3(); var a = new Vec3(); var b = new Vec3(); var c = new Vec3(); var d = new Vec3(); var tmpRaycastResult = new RaycastResult(); /** * @method intersectBox * @private * @param {Shape} shape * @param {Quaternion} quat * @param {Vec3} position * @param {Body} body */ Ray.prototype.intersectBox = function(shape, quat, position, body, reportedShape){ return this.intersectConvex(shape.convexPolyhedronRepresentation, quat, position, body, reportedShape); }; Ray.prototype[Shape.types.BOX] = Ray.prototype.intersectBox; /** * @method intersectPlane * @private * @param {Shape} shape * @param {Quaternion} quat * @param {Vec3} position * @param {Body} body */ Ray.prototype.intersectPlane = function(shape, quat, position, body, reportedShape){ var from = this.from; var to = this.to; var direction = this._direction; // Get plane normal var worldNormal = new Vec3(0, 0, 1); quat.vmult(worldNormal, worldNormal); var len = new Vec3(); from.vsub(position, len); var planeToFrom = len.dot(worldNormal); to.vsub(position, len); var planeToTo = len.dot(worldNormal); if(planeToFrom * planeToTo > 0){ // "from" and "to" are on the same side of the plane... bail out return; } if(from.distanceTo(to) < planeToFrom){ return; } var n_dot_dir = worldNormal.dot(direction); if (Math.abs(n_dot_dir) < this.precision) { // No intersection return; } var planePointToFrom = new Vec3(); var dir_scaled_with_t = new Vec3(); var hitPointWorld = new Vec3(); from.vsub(position, planePointToFrom); var t = -worldNormal.dot(planePointToFrom) / n_dot_dir; direction.scale(t, dir_scaled_with_t); from.vadd(dir_scaled_with_t, hitPointWorld); this.reportIntersection(worldNormal, hitPointWorld, reportedShape, body, -1); }; Ray.prototype[Shape.types.PLANE] = Ray.prototype.intersectPlane; /** * Get the world AABB of the ray. * @method getAABB * @param {AABB} aabb */ Ray.prototype.getAABB = function(result){ var to = this.to; var from = this.from; result.lowerBound.x = Math.min(to.x, from.x); result.lowerBound.y = Math.min(to.y, from.y); result.lowerBound.z = Math.min(to.z, from.z); result.upperBound.x = Math.max(to.x, from.x); result.upperBound.y = Math.max(to.y, from.y); result.upperBound.z = Math.max(to.z, from.z); }; var intersectConvexOptions = { faceList: [0] }; var worldPillarOffset = new Vec3(); var intersectHeightfield_localRay = new Ray(); var intersectHeightfield_index = []; var intersectHeightfield_minMax = []; /** * @method intersectHeightfield * @private * @param {Shape} shape * @param {Quaternion} quat * @param {Vec3} position * @param {Body} body */ Ray.prototype.intersectHeightfield = function(shape, quat, position, body, reportedShape){ var data = shape.data, w = shape.elementSize; // Convert the ray to local heightfield coordinates var localRay = intersectHeightfield_localRay; //new Ray(this.from, this.to); localRay.from.copy(this.from); localRay.to.copy(this.to); Transform.pointToLocalFrame(position, quat, localRay.from, localRay.from); Transform.pointToLocalFrame(position, quat, localRay.to, localRay.to); localRay._updateDirection(); // Get the index of the data points to test against var index = intersectHeightfield_index; var iMinX, iMinY, iMaxX, iMaxY; // Set to max iMinX = iMinY = 0; iMaxX = iMaxY = shape.data.length - 1; var aabb = new AABB(); localRay.getAABB(aabb); shape.getIndexOfPosition(aabb.lowerBound.x, aabb.lowerBound.y, index, true); iMinX = Math.max(iMinX, index[0]); iMinY = Math.max(iMinY, index[1]); shape.getIndexOfPosition(aabb.upperBound.x, aabb.upperBound.y, index, true); iMaxX = Math.min(iMaxX, index[0] + 1); iMaxY = Math.min(iMaxY, index[1] + 1); for(var i = iMinX; i < iMaxX; i++){ for(var j = iMinY; j < iMaxY; j++){ if(this.result._shouldStop){ return; } shape.getAabbAtIndex(i, j, aabb); if(!aabb.overlapsRay(localRay)){ continue; } // Lower triangle shape.getConvexTrianglePillar(i, j, false); Transform.pointToWorldFrame(position, quat, shape.pillarOffset, worldPillarOffset); this.intersectConvex(shape.pillarConvex, quat, worldPillarOffset, body, reportedShape, intersectConvexOptions); if(this.result._shouldStop){ return; } // Upper triangle shape.getConvexTrianglePillar(i, j, true); Transform.pointToWorldFrame(position, quat, shape.pillarOffset, worldPillarOffset); this.intersectConvex(shape.pillarConvex, quat, worldPillarOffset, body, reportedShape, intersectConvexOptions); } } }; Ray.prototype[Shape.types.HEIGHTFIELD] = Ray.prototype.intersectHeightfield; var Ray_intersectSphere_intersectionPoint = new Vec3(); var Ray_intersectSphere_normal = new Vec3(); /** * @method intersectSphere * @private * @param {Shape} shape * @param {Quaternion} quat * @param {Vec3} position * @param {Body} body */ Ray.prototype.intersectSphere = function(shape, quat, position, body, reportedShape){ var from = this.from, to = this.to, r = shape.radius; var a = Math.pow(to.x - from.x, 2) + Math.pow(to.y - from.y, 2) + Math.pow(to.z - from.z, 2); var b = 2 * ((to.x - from.x) * (from.x - position.x) + (to.y - from.y) * (from.y - position.y) + (to.z - from.z) * (from.z - position.z)); var c = Math.pow(from.x - position.x, 2) + Math.pow(from.y - position.y, 2) + Math.pow(from.z - position.z, 2) - Math.pow(r, 2); var delta = Math.pow(b, 2) - 4 * a * c; var intersectionPoint = Ray_intersectSphere_intersectionPoint; var normal = Ray_intersectSphere_normal; if(delta < 0){ // No intersection return; } else if(delta === 0){ // single intersectio