是否有一种算法可以检测2D地图上的“大陆”?


28

在此地图上,“大陆”是指可以在四个基本方向(北,南,东,西-而不是对角线)上连接到地图中心的所有土地。 在此处输入图片说明

我想发现大陆并填补其中的空白。我想到了三件事:

  1. 如果可以使用路径查找算法将每个非水(暗单元)单元连接到地图中心,则进行搜索。太贵了!但这可能适用于这些岛屿。

  2. 大陆上满是一桶绿色的油漆。每个孔都被油漆包围着...现在呢?如果我检查大陆内部的每个水位是否相邻,我将删除海岸线上显示的一些半岛和其他地理特征。

  3. 某种边缘检测可以弄清楚大陆。保留里面的东西,如果是水则填满,除去外面的东西。复杂?

也许一些有游戏经验的开发人员可以帮我解决这个问题,也许可以给我一些已知算法或技术的名称?


4
我只是想知道您是否使用某种算法来生成此地图。如果是这样,您使用了什么?
jgallant 2012年

在基于图块的环境中工作时值得考虑的是Marching Squares算法。有了它,您也可以检测并保留较小的岛,然后按大小排序,丢弃单个单元岛,或者使用任何可能的标准。
William Mariager 2012年

@乔恩是的!菱形平方算法生成高度图,然后所有以下一个值是水,其余部分,土地。我计划将其用作大陆形状,然后再使用一次以添加地形细节。
加百利·A·佐里拉

@Mind Marching Squares算法将很方便地绘制非洲大陆的边界。谢谢很好的建议!
加百利·A·佐里拉2012年

Answers:


32

清除岛屿

我之前在一款游戏中已经做过这类事情。要摆脱外围岛屿,流程基本上是:

  1. 首先,必须确保地图的中心将始终属于主要陆地,并且每个像素开始时都是“土地”或“水”(即不同的颜色)。
  2. 然后,从地图的中心开始进行四方向的洪水填充,并扩散到所有“土地”图块中。将此泛洪填充访问的每个像素标记为不同的类型,例如“ MainLand”。
  3. 最后遍历整个地图,并将所有剩余的“土地”像素转换为“水”,以摆脱其他岛屿。

消除湖泊

至于摆脱岛上的洞(或湖泊),您可以执行类似的过程,但要从地图的各个角落开始,然后通过“水”图块展开。这将使您可以将“海洋”与其他水瓦片区分开,然后就可以摆脱它们,就像之前摆脱岛屿一样。

让我来研究一下我在这里某处的洪水填充的实现方式(免责声明,我并不关心效率,因此,我确定有很多更有效的方法来实现它):

private void GenerateSea()
{
    // Initialize visited tiles list
    visited.Clear();

    // Start generating sea from the four corners
    GenerateSeaRecursive(new Point(0, 0));
    GenerateSeaRecursive(new Point(size.Width - 1, 0));
    GenerateSeaRecursive(new Point(0, size.Height - 1));
    GenerateSeaRecursive(new Point(size.Width - 1, size.Height - 1));
}

private void GenerateSeaRecursive(Point point)
{
    // End recursion if point is outside bounds
    if (!WithinBounds(point)) return;

    // End recursion if the current spot is a land
    if (tiles[point.X, point.Y].Land) return;

    // End recursion if this spot has already been visited
    if (visited.Contains(point)) return;

    // Add point to visited points list
    visited.Add(point);

    // Calculate neighboring tiles coordinates
    Point right = new Point(point.X + 1, point.Y);
    Point left = new Point(point.X - 1, point.Y);
    Point up = new Point(point.X, point.Y - 1);
    Point down = new Point(point.X, point.Y + 1);

    // Mark neighbouring tiles as Sea if they're not Land
    if (WithinBounds(right) && tiles[right.X, right.Y].Empty)
        tiles[right.X, right.Y].Sea = true;
    if (WithinBounds(left) && tiles[left.X, left.Y].Empty)
        tiles[left.X, left.Y].Sea = true;
    if (WithinBounds(up) && tiles[up.X, up.Y].Empty)
        tiles[up.X, up.Y].Sea = true;
    if (WithinBounds(down) && tiles[down.X, down.Y].Empty)
        tiles[down.X, down.Y].Sea = true;

    // Call the function recursively for the neighboring tiles
    GenerateSeaRecursive(right);
    GenerateSeaRecursive(left);
    GenerateSeaRecursive(up);
    GenerateSeaRecursive(down);
}

我将其作为摆脱游戏中湖泊的第一步。打电话之后,我要做的只是:

private void RemoveLakes()
{
    // Now that sea is generated, any empty tile should be removed
    for (int j = 0; j != size.Height; j++)
        for (int i = 0; i != size.Width; i++)
            if (tiles[i, j].Empty) tiles[i, j].Land = true;
}

编辑

根据评论添加一些额外的信息。如果您的搜索空间太大,则在使用递归版本的算法时可能会遇到堆栈溢出的情况。这是关于stackoverflow 的链接(双关语意为:-))到算法的非递归版本,使用a Stack<T>代替(也在C#中匹配我的答案,但应该易于适应其他语言,并且在此还有其他实现)链接)。


11
如果您不能保证中心图块始终是陆地和大陆的一部分,请使用洪水填充将每个地块标记为属于特定岛(地图上每个块上没有斑点ID的每个瓦片都用洪水填充,用如果还没有,则为同一个对象。)然后,删除除块数最多的所有斑点以外的所有斑点。
乔治·达基特

符合GeorgeDuckett所说的-您可以尝试使用Google搜索斑点检测算法:这是使用FTIR进行多点触摸的常见问题。我记得我想出了一个更聪明的算法,但是我不记得我的一生是如何工作的。
乔纳森·迪金森

由于我一直在PHP中遇到堆栈问题,因此我实施了LIFO洪水填充,效果非常好。
加百利·A·佐里拉

仅当从BFS算法调用DFS算法时,它才充满洪水吗?请解释一个人。
jcora 2012年

@贝恩是什么意思?DFS或BFS之间的区别只是访问节点的顺序。我不认为洪水填充算法会指定遍历的顺序-只要它在不重新访问节点的情况下填充整个区域,它就不会在意。顺序取决于实现。有关比较使用队列与使用堆栈时遍历顺序的图片,请参见Wikipedia条目的底部。递归实现也可以视为堆栈(因为它使用了调用堆栈)。
David Gouveia'3


5

这是图像处理中的标准操作。您使用两阶段操作。

首先创建地图的副本。在此地图上,将所有与海洋接壤的陆地像素变成海洋像素。如果执行一次,它将消除2x2岛,并缩小更大的岛。如果执行两次,它将消除4x4岛,等等。

在第二阶段中,您进行的操作几乎完全相反:将与陆地接壤的所有海洋像素都变成陆地像素,但前提是它们是原始地图中的陆地像素(这就是为什么在第一阶段进行了复制)。除非在第一阶段中将其完全消除,否则这会将岛屿恢复为原始形态。


1

我有一个类似的问题,但没有从事游戏开发。我必须在图像中找到彼此相邻且具有相同值(连接区域)的像素。我尝试使用递归泛洪,但一直导致堆栈溢出(我是新手程序员:P)。然后,我尝试使用此方法http://en.wikipedia.org/wiki/Connected-component_labeling,对于我的问题而言,它实际上要高效得多。


您应该尝试过泛洪算法的堆栈版本并保存堆栈问题。结果要比CCL简单得多(我在过去的2周里一直没有运气。)
Gabriel A. Zorrilla

是的,我尝试了两者,但我先让CCL开始工作:P,并认为这是一个好主意。很高兴您解决了您的问题:)
Elegant_Cow 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.