2Dマップで「本土」を検出するアルゴリズムはありますか?


28

このマップでは、「メインランド」は、4つの基本的な方向(北、南、東、西-斜めではない)でマップの中心に接続できるすべての土地です。 ここに画像の説明を入力してください

本土を検出し、本土の穴を埋めたいと思います。私は3つのことを考えました:

  1. パス検索アルゴリズムを使用して、マップの中心に接続できる場合は、すべての非水(暗いセル)セルを検索します。高過ぎ!しかし、これは島ではうまくいく可能性があります。

  2. 本土は緑色の塗料でいっぱいです。各穴はペイントで囲まれています... 本土内のすべてのウォーターポイントで隣接関係を確認すると、海岸線に表示されている半島やその他の地理的特徴を削除します。

  3. 本土を把握するためのある種のエッジ検出。内側のものはすべて保管し、水であればそれを満たし、外側のものは取り除きます。複雑?

おそらく、ゲームの経験豊富な開発者がこれを手伝ってくれて、おそらく既知のアルゴリズムやテクニックの名前を教えてくれるでしょうか?


4
このマップを生成するために何らかのアルゴリズムを使用したのではないかと思っています。もしそうなら、あなたは何を使いましたか?
-jgallant

タイルベースの環境で作業するときに検討する価値があるのは、マーチングスクエアアルゴリズムです。これを使用して、小さな島も検出して保持し、サイズごとに並べ替えて、単一のセルの島または任意の基準を破棄できます。
ウィリアムマリアガー

@ジョンはい!高さマップを生成するためのダイアモンドスクエアアルゴリズム。1つの値以下はすべて水、残り、土地です。これを大陸の形状として使用してから、別のパスを使用して地形の詳細を追加する予定です。
ガブリエルA.ゾリラ

@Mind Marching Squares Algorithmは、大陸の国境を描くのに便利です。いい提案ありがとう!
ガブリエルA.ゾリラ

回答:


32

島の削除

私は自分のゲームでこの種のことを以前にやったことがあります。外側の島を取り除くためのプロセスは基本的に次のとおりでした。

  1. 最初に、マップの中心が常にメインランドに属し、各ピクセルが「土地」または「水」(つまり異なる色)で始まることを保証する必要があります。
  2. 次に、マップの中心から始まり、「土地」タイル全体に広がる4方向の塗りつぶしを行います。この塗りつぶしがアクセスしたすべてのピクセルを、「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;
}

編集

コメントに基づいていくつかの追加情報を追加します。サーチスペースが大きすぎる場合、アルゴリズムの再帰バージョンを使用するときにスタックオーバーフローが発生する可能性があります。ここにスタックオーバーフローのリンク(pun意図:-))をアルゴリズムの非再帰バージョンに使用し、Stack<T>代わりに(私の答えに一致するC#でも使用しますが、他の言語に簡単に適応できる必要があります)リンクも)。


11
中央のタイルが常に本土の一部であると保証できない場合、洪水塗りつぶしを使用して、各土地タイルを特定の島に属するものとしてマークします(マップ上のblob IDのないすべてのタイルからの塗りつぶし、まだ持っていない場合は同じブロビッド)。次に、タイルが最も多いブロブ以外をすべて削除します。
ジョージダケット

GeorgeDuckettの発言に沿って、Googlingブロブ検出アルゴリズムを試してみてください。これは、FTIRを使用したマルチタッチの一般的な問題です。よりスマートなアルゴリズムを思いついたのを覚えていますが、それがどのように機能したかは一生思い出せません。
ジョナサンディキンソン

PHPでスタックの問題が発生しているので、LIFOのフラッドフィルを実装しました。
ガブリエルA.ゾリラ

DFSアルゴリズムがBFSアルゴリズムから呼び出された場合にのみ、フラッドフィルではありませんか?誰か説明してください。
-jcora

@Baneどういう意味ですか?DFSまたはBFSの違いは、ノードがアクセスされる順序だけです。フラッドフィルアルゴリズムがトラバーサルの順序を指定するとは思わない-ノードを再訪せずにリージョン全体を埋める限り、それは気にしない 順序は実装によって異なります。キューを使用する場合とスタックを使用する場合のトラバースの順序を比較した図については、ウィキペディアのエントリの下部を参照してください。再帰的な実装もスタックと見なすことができます(呼び出しスタックを使用するため)。
デヴィッドゴーベイア


5

これは画像処理の標準操作です。2フェーズ操作を使用します。

地図のコピーを作成することから始めます。このマップから、海に接するすべての陸ピクセルを海ピクセルに変換します。これを1回行うと、2x2の島がなくなり、大きな島が縮小します。2回行うと、4x4の島などがなくなります。

フェーズ2では、ほぼ逆の操作を行います。元のマップのランドピクセルである場合にのみ、ランドに接するすべての海のピクセルをランドピクセルにします(これがフェーズ1でコピーを作成した理由です)。これにより、フェーズ1で島が完全に除去されない限り、島は元の形に再成長します。


1

同様の問題がありましたが、ゲーム開発ではありませんでした。画像内で互いに隣接し、同じ値(接続された領域)を持つピクセルを見つける必要がありました。再帰的なフラッドフィルを使用しようとしましたが、スタックオーバーフローを引き起こし続けました(私は初心者プログラマです:P)。その後、この方法を試してみましたhttp://en.wikipedia.org/wiki/Connected-component_labelingそれはとにかく私の問題のために、実際にははるかに効率的でした。


フラッドアルゴのスタックバージョンを試し、スタックの問題を回避する必要がありました。CCLよりもはるかにシンプルになった(私は運と最後の2週間に取り組んできました。)
ガブリエルA. Zorrilla

ええ、私は両方を試しましたが、私は最初にCCLを動作させました:P、それはきちんとしたアイデアだと思いました。問題を解決してくれてうれしいです:)
Elegant_Cow
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.