UNPKG

cannon

Version:

A lightweight 3D physics engine written in JavaScript.

323 lines (278 loc) 7.34 kB
var Shape = require('../shapes/Shape'); var Broadphase = require('../collision/Broadphase'); module.exports = SAPBroadphase; /** * Sweep and prune broadphase along one axis. * * @class SAPBroadphase * @constructor * @param {World} [world] * @extends Broadphase */ function SAPBroadphase(world){ Broadphase.apply(this); /** * List of bodies currently in the broadphase. * @property axisList * @type {Array} */ this.axisList = []; /** * The world to search in. * @property world * @type {World} */ this.world = null; /** * Axis to sort the bodies along. Set to 0 for x axis, and 1 for y axis. For best performance, choose an axis that the bodies are spread out more on. * @property axisIndex * @type {Number} */ this.axisIndex = 0; var axisList = this.axisList; this._addBodyHandler = function(e){ axisList.push(e.body); }; this._removeBodyHandler = function(e){ var idx = axisList.indexOf(e.body); if(idx !== -1){ axisList.splice(idx,1); } }; if(world){ this.setWorld(world); } } SAPBroadphase.prototype = new Broadphase(); /** * Change the world * @method setWorld * @param {World} world */ SAPBroadphase.prototype.setWorld = function(world){ // Clear the old axis array this.axisList.length = 0; // Add all bodies from the new world for(var i=0; i<world.bodies.length; i++){ this.axisList.push(world.bodies[i]); } // Remove old handlers, if any world.removeEventListener("addBody", this._addBodyHandler); world.removeEventListener("removeBody", this._removeBodyHandler); // Add handlers to update the list of bodies. world.addEventListener("addBody", this._addBodyHandler); world.addEventListener("removeBody", this._removeBodyHandler); this.world = world; this.dirty = true; }; /** * @static * @method insertionSortX * @param {Array} a * @return {Array} */ SAPBroadphase.insertionSortX = function(a) { for(var i=1,l=a.length;i<l;i++) { var v = a[i]; for(var j=i - 1;j>=0;j--) { if(a[j].aabb.lowerBound.x <= v.aabb.lowerBound.x){ break; } a[j+1] = a[j]; } a[j+1] = v; } return a; }; /** * @static * @method insertionSortY * @param {Array} a * @return {Array} */ SAPBroadphase.insertionSortY = function(a) { for(var i=1,l=a.length;i<l;i++) { var v = a[i]; for(var j=i - 1;j>=0;j--) { if(a[j].aabb.lowerBound.y <= v.aabb.lowerBound.y){ break; } a[j+1] = a[j]; } a[j+1] = v; } return a; }; /** * @static * @method insertionSortZ * @param {Array} a * @return {Array} */ SAPBroadphase.insertionSortZ = function(a) { for(var i=1,l=a.length;i<l;i++) { var v = a[i]; for(var j=i - 1;j>=0;j--) { if(a[j].aabb.lowerBound.z <= v.aabb.lowerBound.z){ break; } a[j+1] = a[j]; } a[j+1] = v; } return a; }; /** * Collect all collision pairs * @method collisionPairs * @param {World} world * @param {Array} p1 * @param {Array} p2 */ SAPBroadphase.prototype.collisionPairs = function(world,p1,p2){ var bodies = this.axisList, N = bodies.length, axisIndex = this.axisIndex, i, j; if(this.dirty){ this.sortList(); this.dirty = false; } // Look through the list for(i=0; i !== N; i++){ var bi = bodies[i]; for(j=i+1; j < N; j++){ var bj = bodies[j]; if(!this.needBroadphaseCollision(bi,bj)){ continue; } if(!SAPBroadphase.checkBounds(bi,bj,axisIndex)){ break; } this.intersectionTest(bi,bj,p1,p2); } } }; SAPBroadphase.prototype.sortList = function(){ var axisList = this.axisList; var axisIndex = this.axisIndex; var N = axisList.length; // Update AABBs for(var i = 0; i!==N; i++){ var bi = axisList[i]; if(bi.aabbNeedsUpdate){ bi.computeAABB(); } } // Sort the list if(axisIndex === 0){ SAPBroadphase.insertionSortX(axisList); } else if(axisIndex === 1){ SAPBroadphase.insertionSortY(axisList); } else if(axisIndex === 2){ SAPBroadphase.insertionSortZ(axisList); } }; /** * Check if the bounds of two bodies overlap, along the given SAP axis. * @static * @method checkBounds * @param {Body} bi * @param {Body} bj * @param {Number} axisIndex * @return {Boolean} */ SAPBroadphase.checkBounds = function(bi, bj, axisIndex){ var biPos; var bjPos; if(axisIndex === 0){ biPos = bi.position.x; bjPos = bj.position.x; } else if(axisIndex === 1){ biPos = bi.position.y; bjPos = bj.position.y; } else if(axisIndex === 2){ biPos = bi.position.z; bjPos = bj.position.z; } var ri = bi.boundingRadius, rj = bj.boundingRadius, boundA1 = biPos - ri, boundA2 = biPos + ri, boundB1 = bjPos - rj, boundB2 = bjPos + rj; return boundB1 < boundA2; }; /** * Computes the variance of the body positions and estimates the best * axis to use. Will automatically set property .axisIndex. * @method autoDetectAxis */ SAPBroadphase.prototype.autoDetectAxis = function(){ var sumX=0, sumX2=0, sumY=0, sumY2=0, sumZ=0, sumZ2=0, bodies = this.axisList, N = bodies.length, invN=1/N; for(var i=0; i!==N; i++){ var b = bodies[i]; var centerX = b.position.x; sumX += centerX; sumX2 += centerX*centerX; var centerY = b.position.y; sumY += centerY; sumY2 += centerY*centerY; var centerZ = b.position.z; sumZ += centerZ; sumZ2 += centerZ*centerZ; } var varianceX = sumX2 - sumX*sumX*invN, varianceY = sumY2 - sumY*sumY*invN, varianceZ = sumZ2 - sumZ*sumZ*invN; if(varianceX > varianceY){ if(varianceX > varianceZ){ this.axisIndex = 0; } else{ this.axisIndex = 2; } } else if(varianceY > varianceZ){ this.axisIndex = 1; } else{ this.axisIndex = 2; } }; /** * 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} */ SAPBroadphase.prototype.aabbQuery = function(world, aabb, result){ result = result || []; if(this.dirty){ this.sortList(); this.dirty = false; } var axisIndex = this.axisIndex, axis = 'x'; if(axisIndex === 1){ axis = 'y'; } if(axisIndex === 2){ axis = 'z'; } var axisList = this.axisList; var lower = aabb.lowerBound[axis]; var upper = aabb.upperBound[axis]; for(var i = 0; i < axisList.length; i++){ var b = axisList[i]; if(b.aabbNeedsUpdate){ b.computeAABB(); } if(b.aabb.overlaps(aabb)){ result.push(b); } } return result; };