4

我正在画布上绘制 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());
    }
},

如果hit == 0,则循环中断,我们假设没有重叠。否则,我们随机计算一个新的 X/Y 位置并重新开始该过程。

这似乎效率低下。这样做的任何性能提示?

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

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++;
    }
}
4

2 回答 2

1

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

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

您需要让每个圆圈寻找与其重叠的圆圈以外的圆圈。

从算法上讲,您可以使用像四叉树这样的巧妙数据结构来改进这种重叠检测。这将让您立即找到所有圆心在您的圆圈的一个小盒子内的圆圈,并让您以这种方式找到重叠。

但是,如果性能是一个问题,则没有必要那么努力。而是按 x 坐标对圆进行排序,绘制相隔 5 的垂直带,然后将每个圆放入它相交的所有带中。现在对于每个圆圈,您可以搜索它相交的所有波段。

效率的下一步是按 y 坐标对每个波段进行排序,以便您可以在该波段中进行二分搜索,以找到与该波段相交的所有圆圈,这些圆圈足够接近可能与您的圆圈相交。但是这些频段通常应该接近空,所以这并不是什么大胜利。

于 2016-06-10T20:44:16.880 回答
0

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

可能有更有趣和类似有机的程序,但一种简单的方法是按排队顺序细分空间,保持当前框大小的随机截止以考虑不同的半径。像这样的东西:

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 MIN_WIDTH = MIN_HEIGHT = 20;

function f(top_left,bottom_right){
  var w = bottom_right.x - top_left.x - 2 * MIN_WIDTH,
      h = bottom_right.y - top_left.y - 2 * MIN_HEIGHT,

      random_top = top_left.x + MIN_WIDTH 
                 + Math.ceil(Math.random() * w),

      random_left = top_left.y + MIN_HEIGHT
                  + Math.ceil(Math.random() * h),

      random_right = top_left.y + MIN_HEIGHT
                   + Math.ceil(Math.random() * h);

  var rectangle_1 = [top_left
                    ,{x: random_top, y: random_left}],

      rectangle_2 = [{x: top_left.x, y: random_left}
                    ,{x: random_top, y: bottom_right.y}],

      rectangle_3 = [{x: random_top, y: top_left.y}
                    ,{x: bottom_right.x, y: random_right}],

      rectangle_4 = [{x: random_top, y: random_right}
                    ,bottom_right];

  return [rectangle_1, rectangle_2, rectangle_3, rectangle_4];
}

console.log(JSON.stringify(f({x: 0, y: 0}, {x: 200, y: 200})))

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

于 2016-06-11T18:17:18.613 回答