4

キャンバスにさまざまなサイズの 100 個の円を描いていますが、重なってはいけません。これらの円も右から左にアニメーション化され (画面から消えると、キャンバスの右端にループして戻ります)、垂直方向の「ボブ」もいくつかありますが、これも他の円と重なることはありません。

以下は私が現在試みているもので、ブラウザをロックしているようです。円のコレクションをループして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());
        }
    },
};

EDIT : @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

リスト全体が作成された後にのみバブルがステージングされる場合 (ただし、既存のリストからランダムな順序でステージングすることでランダムな外観をシミュレートすることもできます)、ランダムな間隔を作成して衝突検出を完全に回避してみませんか?

おそらくもっと興味深い有機的な手順がありますが、単純な方法の 1 つは、現在のボックス サイズのランダムなカットオフを維持して、さまざまな半径を考慮して、キューに入れられた順序でスペースを分割することです。このようなもの:

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最初にフィードします。次に、結果として得られる 4 つの四角形のそれぞれをフィードしてf、それぞれが再び細分されるようにします。一部の長方形が他の長方形よりも大きくなるように、再帰にランダムな停止を追加します (それらは細分化されないため)。長方形のスペース内にバブルを配置します。それらが衝突することはありませんが、結果として得られる配置は、より有機的な感覚を失う可能性があります。

于 2016-06-11T18:17:18.613 に答える