API Docs for:
Show:

File: physics\EntityQuadTree.js


//=====AABB=====

/**
 Describes the Axis-Aligned Bounding Box format required by the physics system. When setting the coordinates for the corners, keep in mind that the physics system works with the standard 2D coordinate system, where the x axis grows left to right and the y axis grows top to bottom.
 @class AABB
 @param x1 {Number} The x component of the top left corner of the AABB.
 @param y1 {Number} The y component of the top left corner of the AABB.
 @param x2 {Number} The x component of the bottom right corner of the AABB.
 @param y2 {Number} The y component of the bottom right corner of the AABB.
 */
function AABB(x1, y1, x2, y2){
    /**
     The top left corner of the AABB.
     @property topLeft
     @type Vector2D
     */
    this.topLeft = new Vector2D(x1, y1);
    /**
     The bottom right corner of the AABB.
     @property bottomRight
     @type Vector2D
     */
    this.bottomRight = new Vector2D(x2, y2);
}

//=====Ray=====

/**
 Describes a ray used to query the physics system.
 @class Ray
 @param beginX {Number} The x component of the begin point of the ray.
 @param beginY {Number} The y component of the begin point of the ray.
 @param vectorX {Number} The x component of the vector describing the ray.
 @param vectorY {Number} The y component of the vector describing the ray.
 */
function Ray(beginX, beginY, vectorX, vectorY){
    /**
     The begin point of the ray.
     @property begin
     @type Vector2D
     */
    this.begin = new Vector2D(beginX, beginY);
    /**
     Vector describing the ray.
     @property vector
     @type Vector2D
     */
    this.vector = new Vector2D(vectorX, vectorY);
}

//=====Node object=====

function EntityQuadTreeNode(x, y, halfWidth, halfHeight, depth, maximumEntitiesPerLeaf, maximumLeafDepth){
    this.center = new Vector2D(x, y);
    this.extents = {
        halfWidth : halfWidth,
        halfHeight : halfHeight
    };
    this.depth = depth;
    
    //List of entities stored in this node.
    //If the node is not a leaf, the list will be null.
    this.entityList = new LinkedList();
    
    //The node is a leaf by default, so the child nodes are uninitialized.
    this.childNodes = {
        topLeft : null,
        topRight : null,
        bottomLeft : null,
        bottomRight : null
    };
    
    this._maximumEntitiesPerLeaf = maximumEntitiesPerLeaf;
    this._maximumLeafDepth = maximumLeafDepth;
}

EntityQuadTreeNode.prototype._redistributeEntity = function(AABB, entity){
    if(AABB.topLeft.x <= this.center.x){
        
        if(AABB.topLeft.y <= this.center.y){
            //The top left corner intersects the top left node.
            this.childNodes.topLeft._insertEntity(AABB, entity);
        }
        
        if(AABB.bottomRight.y >= this.center.y){
            //The bottom left corner intersects the bottom left node.
            this.childNodes.bottomLeft._insertEntity(AABB, entity);
        }
    }
    
    if(AABB.bottomRight.x >= this.center.x){
        
        if(AABB.topLeft.y <= this.center.y){
            //The top right corner intersects the top right node.
            this.childNodes.topRight._insertEntity(AABB, entity);
        }
        
        if(AABB.bottomRight.y >= this.center.y){
            //The bottom right corner intersects the bottom right node.
            this.childNodes.bottomRight._insertEntity(AABB, entity);
        }
    }
};

EntityQuadTreeNode.prototype._split = function(){
    //Initialize the subnodes for this node.
    var childNodeHalfWidth = this.extents.halfWidth / 2,
        childNodeHalfHeight = this.extents.halfHeight / 2,
        
        xMinusHalfWidth = this.center.x - childNodeHalfWidth,
        xPlusHalfWidth = this.center.x + childNodeHalfWidth,
        yMinusHalfHeight = this.center.y - childNodeHalfHeight,
        yPlusHalfHeight = this.center.y + childNodeHalfHeight;
    
    this.childNodes.topLeft = new EntityQuadTreeNode(xMinusHalfWidth, yMinusHalfHeight, childNodeHalfWidth, childNodeHalfHeight, this.depth + 1, this._maximumEntitiesPerLeaf, this._maximumLeafDepth);
    this.childNodes.topRight = new EntityQuadTreeNode(xPlusHalfWidth, yMinusHalfHeight, childNodeHalfWidth, childNodeHalfHeight, this.depth + 1, this._maximumEntitiesPerLeaf, this._maximumLeafDepth);
    this.childNodes.bottomLeft = new EntityQuadTreeNode(xMinusHalfWidth, yPlusHalfHeight, childNodeHalfWidth, childNodeHalfHeight, this.depth + 1, this._maximumEntitiesPerLeaf, this._maximumLeafDepth);
    this.childNodes.bottomRight = new EntityQuadTreeNode(xPlusHalfWidth, yPlusHalfHeight, childNodeHalfWidth, childNodeHalfHeight, this.depth + 1, this._maximumEntitiesPerLeaf, this._maximumLeafDepth);
    
    var entity;
    
    //Redistribute the entities stored in the node.
    for(var entityNode=this.entityList.getFirst(); entityNode; entityNode=entityNode.next){
        entity = entityNode.value;
        //Redistribute the entity to the nodes that it overlaps.
        this._redistributeEntity(entity.get(Shape)._AABB, entity);
    }
    
    //The node is no longer a leaf.
    this.entityList = null;
};

EntityQuadTreeNode.prototype._insertEntity = function(AABB, entity){

    if(this.entityList){
        //This node is a leaf. Insert the entity into this node.
        this.entityList.insert(0, entity);
        
        //Split the node if it's overflowing and the maximum depth has not been reached.
        if(this.entityList.getLength() > this._maximumEntitiesPerLeaf && this.depth < this._maximumLeafDepth){
            this._split();
        }
    }else{
        //This node is not a leaf. Insert the entity into subnodes it overlaps.
        this._redistributeEntity(AABB, entity);
    }
};

//=====Tree object=====

function EntityQuadTree(worldX, worldY, worldHalfWidth, worldHalfHeight, maximumEntitiesPerLeaf, maximumLeafDepth){
    this._center = new Vector2D(worldX, worldY);
    this._extents = {
        halfWidth : worldHalfWidth,
        halfHeight : worldHalfHeight
    };
    this._root = new EntityQuadTreeNode(worldX, worldY, worldHalfWidth, worldHalfHeight, 0, maximumEntitiesPerLeaf, maximumLeafDepth);
}

EntityQuadTree.prototype.getRoot = function(){
    return this._root;
};

EntityQuadTree.prototype.insert = function(entity){
    this._root._insertEntity(entity.get(Shape)._AABB, entity);
};

EntityQuadTree.prototype.remove = function(entity){
    var leafArray = this.queryLeaves(entity.get(Shape)._AABB);
    
    for(var i=0; i<leafArray.length; ++i){
        leafArray[i].entityList.removeValue(entity);
    }
};

EntityQuadTree.prototype.queryLeaves = (function(){
    
    function findLeaf(node, AABB){
        //If the overlapped node is a leaf, add it to the array.
        if(node.entityList){
            leafArray.push(node);
            
        //Otherwise test for overlap with child nodes.
        }else{
            if(AABB.topLeft.x <= node.center.x){
                
                if(AABB.topLeft.y <= node.center.y){
                    //The top left corner intersects the top left node.
                    findLeaf(node.childNodes.topLeft, AABB);
                }
                
                if(AABB.bottomRight.y >= node.center.y){
                    //The bottom left corner intersects the bottom left node.
                    findLeaf(node.childNodes.bottomLeft, AABB);
                }
            }
            
            if(AABB.bottomRight.x >= node.center.x){
                
                if(AABB.topLeft.y <= node.center.y){
                    //The top right corner intersects the top right node.
                    findLeaf(node.childNodes.topRight, AABB);
                }
                
                if(AABB.bottomRight.y >= node.center.y){
                    //The bottom right corner intersects the bottom right node.
                    findLeaf(node.childNodes.bottomRight, AABB);
                }
            }
        }
    };
    
    var leafArray = [];

    return function(AABB){
        //Reset the leaf array.
        leafArray.length = 0;
        //Find all the leaves for the root node that overlap the AABB.
        findLeaf(this._root, AABB);
        
        return leafArray;
    };
})();

EntityQuadTree.prototype.queryEntities = (function(){
    
    function testOverlapAABBAABB(a, b){
        if(a.bottomRight.x < b.topLeft.x || a.topLeft.x > b.bottomRight.x){
            return false;
        }
        
        if(a.bottomRight.y < b.topLeft.y || a.topLeft.y > b.bottomRight.y){
            return false;
        }
        
        return true;
    }
    
    var testedEntities = [],
        overlappingEntities = [];
    
    return function(AABB){
        var leafArray = this.queryLeaves(AABB),
            entity;
        
        //Reset the arrays.
        testedEntities.length = 0;
        overlappingEntities.length = 0;
        
        for(var i=0; i<leafArray.length; ++i){
            for(var entityNode=leafArray[i].entityList.getFirst(); entityNode; entityNode=entityNode.next){
                entity = entityNode.value;
                
                //If the entity hasn't already been tested...
                if(testedEntities.indexOf(entity) < 0){
                    //Test for overlap.
                    if(testOverlapAABBAABB(AABB, entity.get(Shape)._AABB)){
                        overlappingEntities.push(entity);
                    }
                    //Mark the entity as tested.
                    testedEntities.push(entity);
                }
            }
        }
        
        return overlappingEntities;
    };
})();

EntityQuadTree.prototype.clear = function(){
    this._root.entityList = new LinkedList();
    this._root.childNodes.topLeft = null;
    this._root.childNodes.topRight = null;
    this._root.childNodes.bottomLeft = null;
    this._root.childNodes.bottomRight = null;
};