javascript – 如何对数百个画布圈进行高性能重叠/碰撞检测?

我正在绘制100个不同大小的圆圈到画布,它们不能重叠.这些圆圈也将是从右到左的动画(当它们离开屏幕时环回到画布的右边缘),并且还会有一些垂直的“bob”,它们也不能与任何其他圆形重叠.

以下是我目前正在尝试的,这似乎是锁定浏览器.我循环遍历圆圈集合并执行detectOverlap()函数,并将圆圈集合传递给它.

然后,detectOverlap()函数循环遍历圆圈,执行以下检查:

detectOverlap: function (bubblesArr) {
    while (true) {
        var hit = 0;
        for (var i=0; i<bubblesArr.length; i++) {
            var circle = bubblesArr[i];
            var dx = this._x - circle._x;
            var dy = this._y - circle._y;
            var rr = this._radius + circle._radius;
            if (dx * dx + dy * dy < rr * rr) {
                hit++;
            }
        }
        if (hit == 0) {
            break; // didn't overlap, break out of while loop
        }
        // if we didn't break then there was an overlap somewhere. calc again.
        this._x = Math.round(Math.random() * this.stage.getWidth());
        this._y = Math.round(Math.random() * this.stage.getHeight());
    }
},

如果命中== 0,循环中断,我们假设没有重叠.否则,我们随机计算新的X / Y位置并重新启动该过程.

这似乎效率低下.这样做的任何高效提示?

canvas类(入口点):
这个类是“舞台”,它构建了气泡对象,然后将它们添加到画布中.

var $container;
var listData;
var bubbles = [];

function init(l, c) {
    $container = c;
    listData = l;

    // this just draws the canvas. full-width + 500px tall.
    var stage = new Konva.Stage({
      container: $container.selector,
      width: window.innerWidth,
      height: 500
    });

    // this creates the drawing layer where the bubbles will live
    layer = new Konva.Layer();

    // create an instance of the Bubble class for each element in the list.
    for (var i=0; i<listData.length; i++) {
        bubbles[i] = new celebApp.Bubble.Bubble(listData[i], stage);
    }

    /** TODO:::: FIGURE OUT COLLISION DETECTION */
    for (var i=0; i<bubbles.length; i++) {
        bubbles[i].detectOverlap(bubbles);
    }

    // create the Konva representation for our generated objects
    for (var i=0; i<bubbles.length; i++) {
        var b = bubbles[i];
        layer.add(new Konva.Circle({
            x: b._x,
            y: b._y,
            radius: b._radius,
            fill: b._fill,
            stroke: b._stroke,
            strokeWidth: b._strokeWidth,
        }));
    }

    // add the layer to the stage
    stage.add(layer);
}

泡泡类:
这是表示绘制到屏幕的数据的类.我们需要确保这些对象中没有一个彼此重叠.

var Bubble = function (listData, stage) {
    this.stage = stage;
    this._x = Math.round(Math.random() * stage.getWidth()),
    this._y = Math.round(Math.random() * stage.getHeight()),
    this._radius = Math.round(Math.random() * 80);
    this._fill = 'red';
    this._stroke = 'black';
    this._strokeWidth = 4;
    this._speed = 3;
};
Bubble.prototype = {
    detectOverlap: function (bubblesArr) {
        while (true) {
            var hit = 0;
            for (var i=0; i<bubblesArr.length; i++) {
                var circle = bubblesArr[i];
                var dx = this._x - circle._x;
                var dy = this._y - circle._y;
                var rr = this._radius + circle._radius;
                if (dx * dx + dy * dy < rr * rr) {
                    hit++;
                }
            }
            if (hit == 0) {
                break; // didn't overlap
            }
            this._x = Math.round(Math.random() * this.stage.getWidth());
            this._y = Math.round(Math.random() * this.stage.getHeight());
        }
    },
};

编辑:刚刚根据@MarcB的评论尝试了这个 – 但是,浏览器似乎仍然锁定.是否会导致性能瓶颈,但100个项目都在运行自己的while()循环?

for (var i=0; i<bubblesArr.length; i++) {
    var circle = bubblesArr[i];
    var combinedRadius = Math.abs(circle._radius + this._radius);
    var distance = Math.abs(this._x - circle._x);
    if (distance <= combinedRadius) {
        hit++;
    }
}

最佳答案 这看起来像一个简单的bug.您初始化一个圆圈列表.然后,对于列表中的每个圆圈,您可以计算列表中有多少个圆圈重叠.如果找到重叠,则移动圆圈并重试.

但是每个圆圈都会在列表中找到它并发现它自身重叠.你移动它,同样的事情发生.这是一个永无止境的无限循环.

您需要让每个圆圈查找与其重叠的自身以外的圆圈.

从算法上讲,您可以使用四叉树等智能数据结构来改善此重叠检测.这样您就可以立即找到所有圆圈,这些圆圈的中心位于圆圈的一个小框内,并让您找到重叠的圆圈.

但是,如果性能是一个问题,就没有必要努力工作.而是通过x坐标对圆圈进行排序,绘制相距5的垂直条带,然后将每个圆圈放入与其相交的所有条带中.现在,对于每个圆圈,您只需搜索它相交的所有波段.

效率的下一步将是通过y坐标对每个波段进行排序,以便您可以在该波段中进行二分搜索,以找到与波段相交的所有圆,其距离可能与您的圆相交.但是这些乐队通常应该接近空,所以这并不是一场胜利.

点赞