Javascript 如何对数百个画布圆圈执行重叠/碰撞检测?

Javascript 如何对数百个画布圆圈执行重叠/碰撞检测?,javascript,algorithm,konvajs,Javascript,Algorithm,Konvajs,我在画布上画了100个大小不一的圆圈,它们不能重叠。这些圆也将从右向左设置动画(当它们离开屏幕时循环回到画布的右边缘),并且它们也将有一些垂直的“bob”,这也不能与任何其他圆重叠 下面是我目前正在尝试的,它似乎锁定了浏览器。我循环遍历圆集合并执行detectOverlap()函数,将圆集合传递给它 detectOverlap()函数然后在圆圈中循环,执行以下检查: detectOverlap: function (bubblesArr) { while (true) {

我在画布上画了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());
    }
},
气泡类: 这是表示绘制到屏幕的数据的类。我们需要确保这些物体没有相互重叠

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());
        }
    },
};

这看起来像一个简单的bug。初始化一个圆列表。然后,对于列表中的每个圆,计算列表中有多少圆与之重叠。如果发现重叠,请移动圆并重试

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

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

从算法上讲,您可以使用类似四叉树的智能数据结构来改进这种重叠检测。这会让你立即找到所有圆心在圆的一个小盒子内的圆,并让你通过这种方式找到重叠

然而,如果性能是一个问题,就没有必要那么努力。取而代之的是按x坐标对圆进行排序,画出相距5的垂直带,然后将每个圆放入它相交的所有带中。现在,对于每个圆,您可以搜索它相交的所有带


提高效率的下一步是按y坐标对每个波段进行排序,这样您就可以在该波段中进行二进制搜索,以找到所有与波段相交的圆,这些圆离波段足够近,可能与您的圆相交。但是这些带区通常应该接近于空,因此这不是一个很大的胜利。

如果气泡仅在创建整个列表后才分段(尽管您也可以通过随机顺序从现有列表分段来模拟随机外观),为什么不通过创建随机间隔来避免碰撞检测呢

可能还有更有趣、更有机的过程,但一个简单的方法是按排队顺序细分空间,保持当前框大小的随机截止,以考虑不同的半径。大概是这样的:

Describe the space as a tuple consisting of a middle point and a top left point.

  (middle,top_left)

Choose a random point on the top border of the box:

-----x-----------

Choose one random point on the left border of the box and one on the right:

-----x-----------
|    |          |
|    |          |
x-----          |
|    |          |
|    |----------x
|    |          |
-----------------

You now have four new middle and top left points, easily calculated.

Add the four new tuples to the queue and remove the tuple 
representing the parent box. Customize the function to get appropriately 
sized results.
最后我们得到一个元组列表,这些元组表示不同大小的非重叠框,并带有给定的中间点。剩下的就是随机选取一些框(列表可以散列以避免冲突)并在其中放置气泡。(假设相同水平空间中的气泡以相同的速度移动。)

类似这样的内容(可能需要一些调整):

var最小宽度=最小高度=20;
功能f(左上、右下){
var w=底部右。x-顶部左。x-2*最小宽度,
h=右下角。y-左上角。y-2*最小高度,
random_top=左上角x+最小宽度
+Math.ceil(Math.random()*w),
随机左=左上方。y+最小高度
+Math.ceil(Math.random()*h),
随机右=上左。y+最小高度
+Math.ceil(Math.random()*h);
变量矩形_1=[左上角
,{x:random_top,y:random_left}],
矩形_2=[{x:top_left.x,y:random_left}
,{x:random_top,y:bottom_right.y}],
矩形_3=[{x:random_top,y:top_left.y}
,{x:bottom_right.x,y:random_right}],
矩形_4=[{x:random_top,y:random_right}
,右下角];
返回[rectangle_1,rectangle_2,rectangle_3,rectangle_4];
}
log(JSON.stringify(f({x:0,y:0},{x:200,y:200})))

第一次将整个画布大小放入
f
。然后,将四个结果矩形中的每一个都输入到
f
中,这样每一个都会再次细分,以此类推。向递归添加一个随机停止,这样一些矩形将比其他矩形大(因为这些矩形不会被细分)。将气泡放置在矩形空间内。它们不会发生碰撞,但最终的排列可能会失去一种更自然的感觉——也许可以通过随机“轻推”它们来进行调整。

从技术角度讲,只需检查两个圆的x/y位置在x或y方向上是否在彼此的2个半径范围内,就可以减少大量笛卡尔距离计算。e、 g.半径1和半径2圆,如果一个圆的X位置为5,另一个圆的X位置为10,则它们不可能重叠,因为距离大于它们的组合半径。这只是简单的添加和更多/更少的测试。只有当它们之间的距离在(r1+r2)以内时,才进行完全笛卡尔距离。你有没有理由提到画布(还有JavaScript…和HTML…)?这个问题应该纯粹是算法问题。删除所有不相关的代码块和标记,并解释您期望/需要的行为以及您尝试过的行为。@Amit:在执行此类操作时,语言有时很重要。像Matlab这样的语言或像D3或box2d这样的库有时具有内置函数,这些函数已经为此进行了优化。因为OP不知道解决方案,他不可能知道天气,也不知道语言问题。@MarcB感谢您的输入——我已经更新了我的原始答案,尝试使用您的解决方案,但是浏览器似乎仍然锁定。一旦找到了,为什么还要继续计算点击数?你可以马上跳出这个循环。很有趣。我添加了一些过滤,这样它只会在一个集合上执行减去它本身,但它是
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++;
    }
}
Describe the space as a tuple consisting of a middle point and a top left point.

  (middle,top_left)

Choose a random point on the top border of the box:

-----x-----------

Choose one random point on the left border of the box and one on the right:

-----x-----------
|    |          |
|    |          |
x-----          |
|    |          |
|    |----------x
|    |          |
-----------------

You now have four new middle and top left points, easily calculated.

Add the four new tuples to the queue and remove the tuple 
representing the parent box. Customize the function to get appropriately 
sized results.