用尽可能多的非重叠形状随机填充空间的最有效方法是什么?在我的具体情况下,我正在用圆圈填充圆圈。我随机放置圆圈,直到外圈的一定百分比被填充或一定数量的放置失败(即放置在与现有圆圈重叠的位置)。这非常慢,并且经常留下空白,除非我允许大量失败。
那么,有没有其他类型的填充算法可以用来快速填充尽可能多的空间,但看起来仍然是随机的?
用尽可能多的非重叠形状随机填充空间的最有效方法是什么?在我的具体情况下,我正在用圆圈填充圆圈。我随机放置圆圈,直到外圈的一定百分比被填充或一定数量的放置失败(即放置在与现有圆圈重叠的位置)。这非常慢,并且经常留下空白,除非我允许大量失败。
那么,有没有其他类型的填充算法可以用来快速填充尽可能多的空间,但看起来仍然是随机的?
您遇到了Coupon 收集器的问题,因为您使用的是Rejection sampling技术。
您还对“随机填充”是什么做出了强有力的假设。你的算法会在圆圈之间留下很大的空隙;这就是你所说的“随机”吗?尽管如此,这是一个完全有效的定义,我同意它。
要调整您当前的“随机填充”以避免拒绝采样优惠券收集器的问题,只需将您要填充的空间划分为网格。例如,如果您的圆的半径为 1,则将较大的圆划分为 1/sqrt(2) 宽度块的网格。当填充网格框变得“不可能”时,请在选择新点时忽略该网格框。问题解决了!
但是,您必须小心如何编码!可能的危险:
if (random point in invalid grid){ generateAnotherPoint() }
那么你就会忽略这种优化的好处/核心思想。pickARandomValidGridbox()
那么你会稍微降低在大圆圈边缘附近制作圆圈的可能性(尽管如果你是为图形艺术项目而不是科学或数学项目这样做,这可能没问题);但是,如果将网格大小设为圆半径的 1/sqrt(2) 倍,则不会遇到此问题,因为无法在大圆的边缘绘制块,因此您可以忽略所有网格框在边缘。因此,您避免优惠券收集者问题的方法的概括如下:
Inputs: large circle coordinates/radius(R), small circle radius(r)
Output: set of coordinates of all the small circles
Algorithm:
divide your LargeCircle into a grid of r/sqrt(2)
ValidBoxes = {set of all gridboxes that lie entirely within LargeCircle}
SmallCircles = {empty set}
until ValidBoxes is empty:
pick a random gridbox Box from ValidBoxes
pick a random point inside Box to be center of small circle C
check neighboring gridboxes for other circles which may overlap*
if there is no overlap:
add C to SmallCircles
remove the box from ValidBoxes # possible because grid is small
else if there is an overlap:
increase the Box.failcount
if Box.failcount > MAX_PERGRIDBOX_FAIL_COUNT:
remove the box from ValidBoxes
return SmallCircles
(*) 这一步也是一个重要的优化,我只能假设你还没有。没有它,您的 dosThisCircleOverlapAnother(...) 函数在O(N)
每次查询时的效率都非常低,这将使大比例的圆圈几乎不可能填充R>>r
。
这是您的算法的精确概括,以避免缓慢,同时仍保持其优雅的随机性。
编辑:由于您已评论这是针对游戏的并且您对不规则形状感兴趣,因此您可以将其概括如下。对于任何小的不规则形状,将其包围在一个圆圈中,以表示您希望它与物体之间的距离。您的网格可以是最小地形特征的大小。较大的特征可以包含 1x2 或 2x2 或 3x2 或 3x3 等连续块。请注意,许多具有跨越大距离(山脉)和小距离(火炬)的特征的游戏通常需要递归分割的网格(即一些块被分割成进一步的 2x2 或 2x2x2 子块),从而生成树结构。这种具有大量簿记功能的结构将允许您随机放置连续块,但它需要大量编码。但是,您可以做的是使用圆形网格算法首先放置较大的功能(当地图上有很多空间可以使用并且您可以检查相邻的网格框以获取集合而不会遇到优惠券收集器的问题),然后放置较小的特征。如果您可以按此顺序放置要素,则除了在放置 1x2/3x3/等时检查相邻网格框是否存在冲突外,几乎不需要额外的编码。团体。
产生有趣结果的一种方法是
create an empty NxM grid
create an empty has-open-neighbors set
for i = 1 to NumberOfRegions
pick a random point in the grid
assign that grid point a (terrain) type
add the point to the has-open-neighbors set
while has-open-neighbors is not empty
foreach point in has-open-neighbors
get neighbor-points as the immediate neighbors of point
that don't have an assigned terrain type in the grid
if none
remove point from has-open-neighbors
else
pick a random neighbor-point from neighbor-points
assign its grid location the same (terrain) type as point
add neighbor-point to the has-open-neighbors set
完成后,has-open-neighbors 将为空,并且网格中最多填充 NumberOfRegions 区域(某些具有相同地形类型的区域可能是相邻的,因此将组合形成一个区域)。
使用此算法的示例输出,包含 30 个点、14 种地形类型和 200x200 像素的世界:
编辑:试图澄清算法。
如何使用两步过程:
对于第 2 步,对于每个圆心,您需要知道到其最近邻居的距离。(这可以在 O(n^2) 时间内使用蛮力计算所有点,尽管对于平面中的点可能存在更快的算法。)然后只需将该距离除以 2 即可获得安全半径。(您也可以将其进一步缩小,无论是固定量还是与半径成比例的量,以确保不会接触任何圆。)
要看到这是可行的,请考虑任何点 p 及其最近的邻居 q,它与 p 有一段距离 d。如果 p 也是 q 的最近邻居,那么两个点都将得到半径为 d/2 的圆,因此它们是接触的;OTOH,如果 q 有不同的最近邻,它的距离一定是 d' < d,所以以 q 为中心的圆会更小。所以无论哪种方式,这两个圆圈都不会重叠。
我的想法是从紧凑的网格布局开始。然后取每个圆圈并在某个随机方向上对其进行扰动。您扰动它的距离也可以随机选择(只要确保距离不会使其与另一个圆圈重叠)。
这只是一个想法,我相信您可以通过多种方式对其进行修改和改进。