使用递归的Midpoint Displacement算法

时间:2016-08-18 00:04:51

标签: c++ algorithm recursion procedural-generation

前言

我必须道歉,但我认为自己在实施算法方面非常糟糕,因为我没有做过多次这样的编程,所以请尽量不要生我的气。我已经画了很多关于如何解决这个问题的图片,但是我找不到自己的方式,所以我在寻求你的帮助。

引言

我一直在尝试使用递归在我的游戏中为我的山地地形背景实现Midpoint Displacement算法。我在虚幻引擎4中进行游戏 - 似乎没有一种有效的2D绘制点的方法,因为UE4基本上只支持2D的Paper2D插件,所以我尝试使用tilemap和tiles来实现算法。所以我问的问题非常具体......

实际问题

基本上我现在所拥有的是一个宽度为256,高度为64的tilemap。分割后的tile数量应如下图所示(由于事实,我们必须从每个tile数中减去一个) tile从0开始而不是从1开始:

以下是使用Midpoint Displacement算法后瓷砖数量应该如何以及如何使用我的错误实现将它们放置在虚幻引擎4编辑器中的tilemap中:

Midpoint Displacement algorithm

据我所知,函数调用的计数在每次除法后是指数的(某个值为2的幂)。我实际上并不确定递归是否是使用此算法的最佳方式,所以请随意在此中断。

所以,这里有一些代码。 我手动绘制第一个,中间和最后一个,其余的点只是进一步移动到tilemap的左侧。

在顶部着色的第一个,中间和最后一个瓷砖以及图片底部的中点位移算法功能(粗糙度是目前未使用的变量。

//IRRELEVANT CODE ABOVE
back->SetCell(0, FMath::RandRange(30, testTilemap->MapHeight - 1), contrast_purple);
back->SetCell(testTilemap->MapWidth-1, FMath::RandRange(30, testTilemap->MapHeight - 1), contrast_purple);
back->SetCell(FMath::FloorToInt((0 + testTilemap->MapWidth - 1) / 2), FMath::RandRange(30, testTilemap->MapHeight - 1), contrast_purple);
// Terrain generation
useMidpointDisplacement(FMath::FloorToInt((0 + testTilemap->MapWidth-1)/2), testTilemap->MapHeight, 6, 6);
}

int AProcedural_Map::useMidpointDisplacement(int width, int height, int iteration, float roughness)
{

if (iteration < 1) {
    return 0;
}
back->SetCell(FMath::FloorToInt(width - FMath::Pow(2, iteration)), FMath::RandRange(30, height - 1), contrast_purple);
back->SetCell(FMath::FloorToInt(width + FMath::Pow(2, iteration)), FMath::RandRange(30, height - 1), contrast_purple);

iteration--;

useMidpointDisplacement(FMath::FloorToInt((0 + width)/2), height, iteration, roughness);
return 0;
}

SetCell 函数使用的参数如下:

  • 拼贴X
  • 平铺Y
  • 平铺纹理

我希望有足够的信息让您了解我的问题。如果没有,请不要犹豫,让我提供更多信息。提前谢谢!

1 个答案:

答案 0 :(得分:1)

您的实施存在两个主要问题:

首先,您只是在内部递归调用useMidpointDisplacement。它需要被调用两次才能生成你在图中显示的双重分支调用树,在每次迭代中你得到的瓷砖数量加倍(顺便说一下,“迭代”是一种用词不当的时候递归算法)。

其次,您不是根据中点位移算法计算高度。具体来说,你在哪里计算中点?你在哪里取代它?你在哪里加入粗糙度值?

在每个递归级别,您需要传递两个高度,一个在您要分割的范围的每一端。然后找到两者的平均值并将其置换一个随机量并使用它来设置中点位置。然后在范围的两端和中点之间递归。

int AProcedural_Map::useMidpointDisplacement(int width, int leftHeight, int rightHeight, int iteration, float roughness)
{    
    if (iteration < 0) {
        return 0;
    }
    // compute midpoint:
    int midpointHeight = (leftHeight + rightHeight) / 2;

    // displace it (incorporate roughness here):
    int range = FMath::FloorToInt(FMath::Pow(2, iteration) * roughness);
    midpointHeight += FMath::RandRange(-range, range);

    // clamp:
    if(midpointHeight < 0) midpointHeight = 0;
    if(midpointHeight >= testTilemap->MapHeight) midpointHeight = testTilemap->MapHeight - 1;

    back->SetCell(width, midpointHeight, contrast_purple);

    iteration--;

    // recurse the two sides:
    useMidpointDisplacement(FMath::FloorToInt(width - FMath::Pow(2, iteration)), leftHeight, midpointHeight, iteration, roughness);
    useMidpointDisplacement(FMath::FloorToInt(width + FMath::Pow(2, iteration)), midpointHeight, rightHeight, iteration, roughness);

    return 0;
}

在顶层,将其称为第一个中点,而不是在通话前计算:

//IRRELEVANT CODE ABOVE
int leftHeight = FMath::RandRange(30, testTilemap->MapHeight - 1);
int rightHeight = FMath::RandRange(30, testTilemap->MapHeight - 1);
back->SetCell(0, leftHeight, contrast_purple);
back->SetCell(testTilemap->MapWidth-1, rightHeight, contrast_purple);

// Terrain generation
useMidpointDisplacement(FMath::FloorToInt((testTilemap->MapWidth-1)/2), leftHeight, rightHeight, 6, 0.2f);
}

如果您以浮动方式传递高度并且在调用SetCell时仅转换为int,则可能会获得更好的结果。使用粗糙度值(0.2f是任意的)。