随机有效地放置 100 个圆圈而不重叠的算法?
algorithm to randomly & efficiently place 100 circles without any overlap?
我正在尝试编写一个脚本,将 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 个不同半径的圆放到舞台上。
- 放置必须是随机的,但分布均匀(不能聚集)。
- 放置必须是高性能的 - 这将在移动网络浏览器上执行。
- 圈子不能intersect/overlap其他圈子。
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;
}
}
}
有lots of efficient collision detection algorithms个。他们中的许多人通过将 space 划分为单元格并维护一个单独的数据结构来有效地查找单元格中的其他对象来工作。基本步骤是:
- 为你的新圈子随机指定一个地点
- 确定它在哪些单元格中
- 在每个单元格中查找碰撞
- 如果发生碰撞,转到 1。
- 否则,将新圆添加到它重叠的每个单元格中。
您可以为单元格数据结构使用简单的正方形网格(即二维数组),或类似 quadtree 的其他内容。在某些情况下,您还可以通过首先尝试便宜但粗略的碰撞检查(边界框是否重叠)来获得额外的速度,如果 returns 为真,请尝试稍微更昂贵和精确的检查。
更新
对于四叉树,查看 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 个位置。
我正在尝试编写一个脚本,将 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 个不同半径的圆放到舞台上。
- 放置必须是随机的,但分布均匀(不能聚集)。
- 放置必须是高性能的 - 这将在移动网络浏览器上执行。
- 圈子不能intersect/overlap其他圈子。
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;
}
}
}
有lots of efficient collision detection algorithms个。他们中的许多人通过将 space 划分为单元格并维护一个单独的数据结构来有效地查找单元格中的其他对象来工作。基本步骤是:
- 为你的新圈子随机指定一个地点
- 确定它在哪些单元格中
- 在每个单元格中查找碰撞
- 如果发生碰撞,转到 1。
- 否则,将新圆添加到它重叠的每个单元格中。
您可以为单元格数据结构使用简单的正方形网格(即二维数组),或类似 quadtree 的其他内容。在某些情况下,您还可以通过首先尝试便宜但粗略的碰撞检查(边界框是否重叠)来获得额外的速度,如果 returns 为真,请尝试稍微更昂贵和精确的检查。
更新
对于四叉树,查看 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 个位置。