碰撞检测优化-四叉树

游戏开发 同时被 2 个专栏收录
2 篇文章 0 订阅

经过测试得出的结论:

检索的对象比较多时,四叉树检索效率明显优于暴力检索。

演示代码 index.html

<body style="margin: 0px;">
    <button style="position: fixed; margin-top: 70px; margin-left: 30px;">当前算法:四叉树检索</button>
    <canvas></canvas>
</body>
<script>
    const COLOR_WHITE = "rgb(255,255,255)";
    const COLOR_GRAY = "rgb(155,155,155)";
    const COLOR_RED = "rgb(255,0,0)";
    const COLOR_BLUE = "rgb(0,0,255)";
    const canvas = document.querySelector("canvas");
    const ctx = canvas.getContext("2d");
    
    /**定义矩形类 */
    const Rect = (function() {
        function Rect() {
            this.x = Math.random() * innerWidth;
            this.y = Math.random() * innerHeight;
            this.width = 10;
            this.height = 10;
            this.speedX = Math.random() * 10 - 5;
            this.speedY = Math.random() * 10 - 5;
            this.color = COLOR_GRAY;
        }
        /**判断是否与指定矩形相交 */
        Rect.prototype.intersects = function(rect) {
            let maxax = this.x + this.width,
                maxay = this.y + this.height,
                maxbx = rect.x + rect.width,
                maxby = rect.y + rect.height;
            return !(maxax < rect.x || maxbx < this.x || maxay < rect.y || maxby < this.y);
        }
        Rect.pool = [];
        Rect.initPool = function() {
            for (let i = 0; i < 3000; i++) {
                this.pool.push(new Rect());
            }
        }
        Rect.updatePool = function(canvas) {
            for (let rect of this.pool) {
                ctx.fillStyle = rect.color;
                ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
                rect.x += rect.speedX;
                rect.y += rect.speedY;
                rect.x < 0 && (rect.speedX = Math.abs(rect.speedX));
                rect.y < 0 && (rect.speedY = Math.abs(rect.speedY));
                rect.x > (innerWidth - rect.width) && (rect.speedX = -Math.abs(rect.speedX));
                rect.y > (innerHeight - rect.height) && (rect.speedY = -Math.abs(rect.speedY));
            }
        }
        return Rect;
    }());
    Rect.initPool();

    /**四叉树 */
    const QuadTree = (function() {
        function QuadTree(bounds, max_objects, max_levels, level) {
            this.max_objects = max_objects || 10; //最大对象存储数
            this.max_levels = max_levels || 5; //树的最大深度,从0开始
            this.level = level || 0; //树的当前深度
            this.bounds = bounds; //界限信息{x: number, y: number, width: number, height: number},锚点是左上角
            this.objects = []; //存储对象
            this.nodes = []; //子节点
        }
        /**使当前节点分裂出四个子节点 */
        QuadTree.prototype.split = function() {
            let nextLevel = this.level + 1;
            let subWidth = this.bounds.width / 2;
            let subHeight = this.bounds.height / 2;
            let x = this.bounds.x;
            let y = this.bounds.y;

            this.nodes[0] = new QuadTree({
                x: x + subWidth,
                y: y,
                width: subWidth,
                height: subHeight
            }, this.max_objects, this.max_levels, nextLevel);

            this.nodes[1] = new QuadTree({
                x: x,
                y: y,
                width: subWidth,
                height: subHeight
            }, this.max_objects, this.max_levels, nextLevel);

            this.nodes[2] = new QuadTree({
                x: x,
                y: y + subHeight,
                width: subWidth,
                height: subHeight
            }, this.max_objects, this.max_levels, nextLevel);

            this.nodes[3] = new QuadTree({
                x: x + subWidth,
                y: y + subHeight,
                width: subWidth,
                height: subHeight
            }, this.max_objects, this.max_levels, nextLevel);
        };
        /**检索目标对象在当前节点的哪个子节点 */
        QuadTree.prototype.getIndexes = function(object) {
            let indexes = [];
            let centerX = this.bounds.x + (this.bounds.width / 2);
            let centerY = this.bounds.y + (this.bounds.height / 2);
            let top = object.y < centerY;
            let left = object.x < centerX;
            let right = object.x + object.width > centerX;
            let bottom = object.y + object.height > centerY;
            top && right && indexes.push(0);
            top && left && indexes.push(1);
            bottom && left && indexes.push(2);
            bottom && right && indexes.push(3);
            return indexes;
        };
        /**向四叉树插入待检索对象 */
        QuadTree.prototype.insert = function(object) {
            if (this.nodes.length) {
                let indexes = this.getIndexes(object);
                for (let index of indexes) {
                    this.nodes[index].insert(object);
                }
                return;
            }

            this.objects.push(object);

            if (this.objects.length > this.max_objects && this.level < this.max_levels) {
                !this.nodes.length && this.split();
                for (let object of this.objects) {
                    let indexes = this.getIndexes(object);
                    for (let index of indexes) {
                        this.nodes[index].insert(object);
                    }
                }
                this.objects = [];
            }
        };
        /**检索出可能与该对象发生碰撞的对象集合 */
        QuadTree.prototype.retrieve = function(object) {
            let returnObjects = this.objects;
            if (this.nodes.length) {
                let indexes = this.getIndexes(object);
                for (let index of indexes) {
                    returnObjects = returnObjects.concat(this.nodes[index].retrieve(object));
                }
            }
            returnObjects = returnObjects.filter((item, index) => {
                return  returnObjects.indexOf(item) == index;
            });
            return returnObjects;
        };
        /**清空四叉树的内容 */
        QuadTree.prototype.clear = function() {
            this.objects = [];
            for (let node of this.nodes) {
                node.clear();
            }
            this.nodes = [];
        };
        return QuadTree;
    }());

    let useQuadTree = true;
    let tapBtn = document.querySelector("button");
    tapBtn.addEventListener("click", () => {
        useQuadTree = !useQuadTree;
        tapBtn.innerText = "当前算法:" + (useQuadTree ? "四叉树" : "暴力") + "检索";
    });

    /**主循环 */
    function loop() {
        requestAnimationFrame(loop);

        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        Rect.updatePool();

        let retrieveStartTime = Date.now();

        if (useQuadTree) { //四叉树检索
            let tree = new QuadTree({x: 0, y: 0, width: canvas.width, height: canvas.height});        
            for (let rect of Rect.pool) {
                rect.color = COLOR_GRAY;
                tree.insert(rect);
            }
            for (let rect of Rect.pool) {
                let targets = tree.retrieve(rect);
                for (let target of targets) {
                    if (rect == target) continue;
                    if (rect.intersects(target)) {
                        rect.color = COLOR_RED;
                        target.color = COLOR_RED;
                    }
                }
            }
            tree.clear();
        } else { //暴力检索
            for (let rect of Rect.pool) {
                rect.color = COLOR_GRAY;
            }
            for (let i = 0; i < Rect.pool.length; i++) {
                for (let j = i + 1; j < Rect.pool.length; j++) {
                    let rectA = Rect.pool[i];
                    let rectB = Rect.pool[j];
                    if (rectA.intersects(rectB)) {
                        rectA.color = COLOR_RED;
                        rectB.color = COLOR_RED;
                    }
                }
            }
        }

        let retrieveTime = Date.now() - retrieveStartTime;
        ctx.fillStyle = COLOR_BLUE;
        ctx.font = "24px Arial";
        ctx.textAlign = "left";
        ctx.textBaseline = "top";
        ctx.fillText("检索耗时:" + parseInt(retrieveTime) + " ms", 30, 30);
    }
    requestAnimationFrame(loop);
</script>

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值