js版四叉树的算法解析和使用

        //根据环境创建一棵树
        //比如你把屏幕当做根节点
        //每一个节点都会有容纳的最大目标物体,超过这个目标物体就会拆分四个节点
        //四叉树也会有深度的控制,就是从根节点到叶子结点的长度
        var quadTree = Quadtree(bounds, max_objects, max_levels, level);
         //往这棵树上插入一个孩子
         //其实对于一颗四叉树而言,它存储的目标物体都是放在叶子结点的四个象限中
         //我们寻找这些目标物体,都是去看当前节点是属于哪个叶子节点,然后根据象限找出目标物体
         //超过最大节点就会拆分
         //不拆分的话就会把目标物体放到对应的象限中
         //可能存在一个孩子属于多个象限的情况,呵呵,那就都存起来了,最后去重就好了
         quadTree.insert(child);
         //判断某一个孩子它可能碰到的所有孩子节点
         //这里的判断是,一级一级往下找,要找出这个child的属于哪个节点
         //根据找到的节点,再找出它属于哪个象限,这个象限可能是多个的,最多四个
         //根据象限找出所有的孩子
         //最后根据找出的孩子,再来进行去重,因为有可能一个孩子会占用多个象限
         var collisionObjects = quadTree.retrieve(child) 
         //到此为止四叉树功能完毕
         
         //每一帧处理碰撞前,都要重新更新这颗四叉树
         
         //每一个孩子身上应该都要有一个包围盒
         

算法介绍

四叉树首先是一棵树,树上有很多节点,每个节点有四个孩子,从根节点散发出来;
对于这个节点的信息其中比较重要的就是它所占领的区域(x,y,width,height)以及一个存放在这片区域里的目标物体数组,这一些目标物体就是一个个包围盒,每个包围盒都有有位置和宽高(x,y,width,height),对区域里的目标个数是有限制的max_object,当超过最大限制,就又开始拆分4个区域,并且将当前区域里的目标对象重新分到四个孩子区域里,这个拆分区域的深度也是有限制的max_levels

成员属性 bounds:场景位置和尺寸(x,y,width,height),一般就是屏幕,最开始创建的为根节点
成员属性max_object:单位区域里最大容纳目标物体的个数,超过这个数目就开始拆分
成员属性max_levels:最大节点深度,控制这树的高度
成员属性level:当前节点的深度
成员属性 objects:存放所有目标对象,如果当前节点有孩子节点,那么这个数组是为空的,
成员属性 nodes:存放孩子节点,这个要么是空的,要么是放置四个孩子节点

初始化一个节点的函数
成员函数Quadtree(bounds, max_objects, max_levels, level);

获取目标对象所属于的象限,这个返回的是数组
巧妙的解决了一个目标物体占用多个象限的问题
成员函数getIndex(pRect):

拆分节点生成四个孩子节点
成员函数split();

插入一个目标对象进来
如果当前节点有孩子节点,则往孩子节点里插入
如果当前没有孩子节点,并且插入的目标物体的数目超过最大值,那么开始拆分节点,重新插入当前节点的所有目标物体
成员函数insert(pRect):

获取所有可以被当前这个目标物体碰撞的物体
先从根节点入手,将根节点的所有目标对象全部存起来
再判断当前的目标范围属于哪个象限,找到以后,再把对应象限的目标物体全部存起来
去重(有目标对象占用多个象限的问题)
递归下去
成员函数retrieve(pRect)

一些基础常识:
1:如果一个节点有孩子,那么这个节点本身是不存储目标物体的,它的所有目标物体都存放在孩子节点中
2:对于一个分布在多个象限(左上0,左下1,右上2,右下3)的目标物体,都是会被存下来的,比如目标物体A,它存在(1,3)象限,当外界传来一个区域,我们首先计算这个区域里属于哪个节点,这个区域最多可以属于四个节点(因为每个节点最多有四个孩子,他代表这个四个象限),我们会从这些节点中取出所有的目标物体,最后还要去重

上代码:

/**
 * quadtree-js
 * @version 1.2.2
 * @license MIT
 * @author Timo Hausmann
 */

 /* https://github.com/timohausmann/quadtree-js.git v1.2.2 */
 
/*
Copyright © 2012-2020 Timo Hausmann

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() {
     
    /**
     * Quadtree Constructor
     * @param Object bounds            bounds of the node { x, y, width, height }
     * @param Integer max_objects      (optional) max objects a node can hold before splitting into 4 subnodes (default: 10)
     * @param Integer max_levels       (optional) total max levels inside root Quadtree (default: 4) 
     * @param Integer level            (optional) deepth level, required for subnodes (default: 0)
     */
    function Quadtree(bounds, max_objects, max_levels, level) {
        
        this.max_objects    = max_objects || 10;
        this.max_levels     = max_levels || 4;
        
        this.level  = level || 0;
        this.bounds = bounds;
        
        this.objects    = [];
        this.nodes      = [];
    };
    
    
    /**
     * Split the node into 4 subnodes
     */
    Quadtree.prototype.split = function() {
        
        var nextLevel   = this.level + 1,
            subWidth    = this.bounds.width/2,
            subHeight   = this.bounds.height/2,
            x           = this.bounds.x,
            y           = this.bounds.y;        
     
        //top right node
        this.nodes[0] = new Quadtree({
            x       : x + subWidth, 
            y       : y, 
            width   : subWidth, 
            height  : subHeight
        }, this.max_objects, this.max_levels, nextLevel);
        
        //top left node
        this.nodes[1] = new Quadtree({
            x       : x, 
            y       : y, 
            width   : subWidth, 
            height  : subHeight
        }, this.max_objects, this.max_levels, nextLevel);
        
        //bottom left node
        this.nodes[2] = new Quadtree({
            x       : x, 
            y       : y + subHeight, 
            width   : subWidth, 
            height  : subHeight
        }, this.max_objects, this.max_levels, nextLevel);
        
        //bottom right node
        this.nodes[3] = new Quadtree({
            x       : x + subWidth, 
            y       : y + subHeight, 
            width   : subWidth, 
            height  : subHeight
        }, this.max_objects, this.max_levels, nextLevel);
    };
    
    
    /**
     * Determine which node the object belongs to
     * @param Object pRect      bounds of the area to be checked, with x, y, width, height
     * @return Array            an array of indexes of the intersecting subnodes 
     *                          (0-3 = top-right, top-left, bottom-left, bottom-right / ne, nw, sw, se)
     */
    Quadtree.prototype.getIndex = function(pRect) {
        
        var indexes = [],
            verticalMidpoint    = this.bounds.x + (this.bounds.width/2),
            horizontalMidpoint  = this.bounds.y + (this.bounds.height/2);    

        var startIsNorth = pRect.y < horizontalMidpoint,
            startIsWest  = pRect.x < verticalMidpoint,
            endIsEast    = pRect.x + pRect.width > verticalMidpoint,
            endIsSouth   = pRect.y + pRect.height > horizontalMidpoint;    

        //top-right quad
        if(startIsNorth && endIsEast) {
            indexes.push(0);
        }
        
        //top-left quad
        if(startIsWest && startIsNorth) {
            indexes.push(1);
        }

        //bottom-left quad
        if(startIsWest && endIsSouth) {
            indexes.push(2);
        }

        //bottom-right quad
        if(endIsEast && endIsSouth) {
            indexes.push(3);
        }
     
        return indexes;
    };
    
    
    /**
     * Insert the object into the node. If the node
     * exceeds the capacity, it will split and add all
     * objects to their corresponding subnodes.
     * @param Object pRect        bounds of the object to be added { x, y, width, height }
     */
    Quadtree.prototype.insert = function(pRect) {
        
        var i = 0,
            indexes;
         
        //if we have subnodes, call insert on matching subnodes
        if(this.nodes.length) {
            indexes = this.getIndex(pRect);
     
            for(i=0; i<indexes.length; i++) {
                this.nodes[indexes[i]].insert(pRect);     
            }
            return;
        }
     
        //otherwise, store object here
        this.objects.push(pRect);

        //max_objects reached
        if(this.objects.length > this.max_objects && this.level < this.max_levels) {

            //split if we don't already have subnodes
            if(!this.nodes.length) {
                this.split();
            }
            
            //add all objects to their corresponding subnode
            for(i=0; i<this.objects.length; i++) {
                indexes = this.getIndex(this.objects[i]);
                for(var k=0; k<indexes.length; k++) {
                    this.nodes[indexes[k]].insert(this.objects[i]);
                }
            }

            //clean up this node
            this.objects = [];
        }
     };
     
     
    /**
     * Return all objects that could collide with the given object
     * @param Object pRect      bounds of the object to be checked { x, y, width, height }
     * @Return Array            array with all detected objects
     */
    Quadtree.prototype.retrieve = function(pRect) {
         
        var indexes = this.getIndex(pRect),
            returnObjects = this.objects;
            
        //if we have subnodes, retrieve their objects
        if(this.nodes.length) {
            for(var i=0; i<indexes.length; i++) {
                returnObjects = returnObjects.concat(this.nodes[indexes[i]].retrieve(pRect));
            }
        }

        //remove duplicates
        returnObjects = returnObjects.filter(function(item, index) {
            return returnObjects.indexOf(item) >= index;
        });
     
        return returnObjects;
    };
    
    
    /**
     * Clear the quadtree
     */
    Quadtree.prototype.clear = function() {
        
        this.objects = [];
     
        for(var i=0; i < this.nodes.length; i++) {
            if(this.nodes.length) {
                this.nodes[i].clear();
              }
        }

        this.nodes = [];
    };

    //export for commonJS or browser
    if(typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        module.exports = Quadtree;
    } else {
        window.Quadtree = Quadtree;    
    }

})();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值