随机算法有效地放置100个圆圈而没有任何重叠?

时间:2016-07-01 18:21:52

标签: javascript algorithm heuristics

我正在尝试编写一个脚本,将100个不同大小的圆圈放在舞台上。我已经概述了下面简明扼要的要求。

鉴于以下内容:

var stage;       // contains a "width" and "height" property.
var circle;      // the circle class. contains x, y, radius & a unique id property.
var circleArray; // contains 100 circle instances

要求:

  • 编写一个函数,在舞台上放置100个不同半径的圆圈。
  • 展示位置必须是随机的,但要均匀分布(没有聚集)。
  • 展示位置必须具有效果 - 这将在移动网络浏览器上执行。
  • 圆圈不得与其他圆相交/重叠。
  • circle.x >= 0必须是真的。
  • circle.y >= 0 && circle.y <= stage.height必须是真的。
  • 圆圈可以具有以下任意半径尺寸(在创建时分配):
    • 150
    • 120
    • 90
    • 80
    • 65

我目前的尝试是一种蛮力方法,无法有效运作。如果我尝试插入超过10个圆圈,浏览器会挂起。下面是我目前的实现,我完全可以抛弃更有效率/更好的一个。

这是一个现场演示(注意:没有实际的绘图代码,只有逻辑,但它仍然会锁定浏览器,所以要警告!!)http://jsbin.com/muhiziduxu/2/edit?js,console

function adjustForOverlap (circleArray) {
  // a reference to the circle that is invoking this function.
  var _this = this;

  // remove this circle from the array we are iterating over.
  var arr = circleArray.filter(function (circle){
    return circle.id !== _this.id;
  });

  // while repeat == true, the circle may be overlapping something.
  var repeat = true;
  while(repeat) {
    var hasOverlap = false;
    for (var i=0; i<arr.length; i++) {
      var other = arr[i];
      var dx = _self.x - other.x;
      var dy = _self.y - other.y;
      var rr = _self.radius + other.radius;
      if (dx * dx + dy * dy < rr * rr) {
        // if here, then an overlap was detected.
        hit = true;
        break;
      }
    }
    // if hit is false, the circle didn't overlap anything, so break.
    if (hit === false) {
      repeat = false;
      break;
    } else {
      // an overlap was detected, so randomize position.
      _self.x = Math.random() * (stage.width*2);
      _self.y = Math.random() * stage.height;
    }
  }
}

1 个答案:

答案 0 :(得分:2)

lots of efficient collision detection algorithms。他们中的许多人通过将空间划分为单元并保持单独的数据结构以及有效查找单元中的其他对象来工作。基本步骤是:

  1. 确定新圈子的随机位置
  2. 确定
  3. 中的哪些单元格
  4. 查看每个单元格中的碰撞
  5. 如果发生碰撞,请转到1。
  6. 否则,将新圆圈添加到它重叠的每个单元格中。
  7. 您可以使用简单的方格(即2维阵列)作为单元格数据结构,或其他类似quadtree的内容。在某些情况下,您还可以通过首先尝试廉价但粗略的碰撞检查(边界框重叠)获得一些额外的速度,如果返回true,请尝试稍微更昂贵和精确的检查。

    <强>更新

    对于四叉树,请查看d3-quadtree,它应该为您提供相当不错的实现,并提供示例。

    对于(非常快速,未经测试的)二维数组实现:

    function Grid(radius, width, height) {
        // I'm not sure offhand how to find the optimum grid size.
        // Let's use a radius as a starting point
        this.gridX = Math.ceil(width / radius);
        this.gridY = Math.ceil(height / radius);
        // Determine cell size
        this.cellWidth = width / this.gridX;
        this.cellHeight = height / this.gridY;
        // Create the grid structure
        this.grid = [];
        for (var i = 0; i < gridY; i++) {
            // grid row
            this.grid[i] = [];
            for (var j = 0; j < gridX; j++) {
                // Grid cell, holds refs to all circles
                this.grid[i][j] = []; 
            }
        }
    }
    
    Grid.prototype = {
        // Return all cells the circle intersects. Each cell is an array
        getCells: function(circle) {
            var cells = [];
            var grid = this.grid;
            // For simplicity, just intersect the bounding boxes
            var gridX1Index = Math.floor(
                (circle.x - circle.radius) / this.cellWidth
            );
            var gridX2Index = Math.ceil(
                (circle.x + circle.radius) / this.cellWidth
            );
            var gridY1Index = Math.floor(
                (circle.y - circle.radius) / this.cellHeight
            );
            var gridY2Index = Math.ceil(
                (circle.y + circle.radius) / this.cellHeight
            );
            for (var i = gridY1Index; i < gridY2Index; i++) {
                for (var j = gridX1Index; j < gridX2Index; j++) {
                    // Add cell to list
                    cells.push(grid[i][j]);
                }
            }
            return cells;
        },
        add: function(circle) {
            this.getCells(circle).forEach(function(cell) {
                cell.push(circle);
            });
        },
        hasCollisions: function(circle) {
            return this.getCells(circle).some(function(cell) {
                return cell.some(function(other) {
                    return this.collides(circle, other);
                }, this);
            }, this);
        },
        collides: function (circle, other) {
            if (circle === other) {
              return false;
            }
            var dx = circle.x - other.x;
            var dy = circle.y - other.y;
            var rr = circle.radius + other.radius;
            return (dx * dx + dy * dy < rr * rr);
        }
    };
    
    var g = new Grid(150, 1000, 800);
    g.add({x: 100, y: 100, radius: 50});
    g.hasCollisions({x: 100, y:80, radius: 100});
    

    以下是一个功能齐全的示例:http://jsbin.com/cojoxoxufu/1/edit?js,output

    请注意,这仅显示30个圆圈。看起来问题通常无法解决您当前的半径,宽度和高度。这样设置为在放弃并接受碰撞之前为每个圆圈寻找多达500个位置。