【问题标题】:Randomly and efficiently filling space with shapes用形状随机有效地填充空间
【发布时间】:2011-07-05 17:11:48
【问题描述】:

用尽可能多的非重叠形状随机填充空间的最有效方法是什么?在我的具体情况下,我正在用圆圈填充圆圈。我随机放置圆圈,直到外圈的一定百分比被填充或一定数量的放置失败(即放置在与现有圆圈重叠的位置)。这很慢,而且通常会留下空白,除非我允许大量失败。

那么,有没有其他类型的填充算法可以用来快速填充尽可能多的空间,但看起来仍然是随机的?

【问题讨论】:

  • 随机是一个很难正确使用的词。你只是想让它看起来杂乱无章吗?你放置的圆圈的大小也一样吗?
  • 无序将是我想要的一个很好的描述。这是游戏中地形生成算法的初始阶段,其中每个圆圈都成为一个独特的地形特征。我只是想确保不规则的布局(即不是网格)。
  • 圆圈可以有不同的大小。但我不介意听到需要同质形状的算法。
  • 这和圆的最大堆积密度有关吗?我相信随机和有效的包装是这个问题的相反设计目标。 mathworld.wolfram.com/CirclePacking.html
  • @Brian Stinar:是的,我明白你的意思。根据定义,最大包装不能有任何随机性。我想要一些非常接近最大包装的东西,但有足够的变化看起来很自然(就像 tskuzzy 建议的那样)。

标签: algorithm


【解决方案1】:

您遇到的问题

您遇到了Coupon collector's problem,因为您使用了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/等时检查相邻网格框是否存在冲突外,几乎不需要额外的编码。组。

【讨论】:

  • 我确定我遗漏了一些明显的东西,但是 r/sqrt(2) 作为网格宽度的意义是什么?为什么这是最佳选择?
【解决方案2】:

产生有趣结果的一种方法是

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 像素的世界:

编辑:试图澄清算法。

【讨论】:

  • 我不太了解你的伪代码。最初的 for 循环是否填充了“有开放邻居”列表?如果不是,while 循环在做什么?
  • for 循环创建初始种群,while 循环在扩展地形的同时更新种群,直到网格中没有未分配的点。有关 C# 实现,请参阅 gist.github.com/1067469。大约需要 8 秒(在我的盒子上)使用 14 种地形类型填充 1000x1000 的网格,其中包含 300 个区域。
【解决方案3】:

如何使用两步流程:

  1. 随机选择一堆 n 点 - 这些将成为圆的中心。
  2. 确定这些圆的半径,使它们不重叠。

对于第 2 步,对于每个圆心,您需要知道到其最近邻居的距离。 (这可以在 O(n^2) 时间内使用蛮力计算所有点,尽管对于平面中的点可能存在更快的算法。)然后只需将该距离除以 2 即可获得安全半径。 (您也可以将其进一步缩小,无论是按固定量还是按与半径成比例的量,以确保不会接触任何圆圈。)

要查看此方法是否有效,请考虑任何点 p 及其最近邻点 q,该点与 p 有一段距离 d。如果 p 也是 q 的最近邻居,那么两个点都将得到半径为 d/2 的圆,因此它们是接触的; OTOH,如果 q 有不同的最近邻,它的距离一定是 d'

【讨论】:

    【解决方案4】:

    我的想法是从紧凑的网格布局开始。然后取每个圆圈并在某个随机方向上对其进行扰动。您扰动它的距离也可以随机选择(只要确保距离不会使其与另一个圆圈重叠)。

    这只是一个想法,我相信您可以通过多种方式对其进行修改和改进。

    【讨论】:

      猜你喜欢
      • 2016-02-20
      • 2013-06-27
      • 2014-05-21
      • 1970-01-01
      • 2018-12-08
      • 1970-01-01
      • 1970-01-01
      • 2019-10-10
      • 1970-01-01
      相关资源
      最近更新 更多