#

API Docs for: 0.6.0
Show: Inherited Protected Private Deprecated

# File: src/collision/SAPBroadphase.js

    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.doBoundingSphereBroadphase(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 axis;
        if(axisIndex === 0){ axis = 'x'; }
        if(axisIndex === 1){ axis = 'y'; }
        if(axisIndex === 2){ axis = 'z'; }

        var biPos = bi.position[axis],
            ri = bi.boundingRadius,
            bjPos = bj.position[axis],
            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.aabb.upperBound[axis] > lower && b.aabb.lowerBound[axis] < upper && b.aabb.overlaps(aabb)){
                result.push(b);
            }
        }

        return result;
    };