小さいボックスから作成できる最大のボックスを抽出する私のアルゴリズムは遅すぎる


9

キューブベースのワールド(Minecraft、Trove、Cube Worldなど)を想像してください。すべてが同じサイズのキューブで構成され、すべてのキューブが同じ種類です。

目標は、最も少ない数の長方形のボックスで世界を表すことです(立方体をマージしますが、凸状の形状(長方形のボックスの形状)を保持します)。私のアルゴリズムはこれで成功しましたが、そのパフォーマンスは意図した目的には遅すぎます。より速いアルゴリズムはありますか?

私のアルゴリズムの疑似C#コードは基本的に次のとおりです。

struct Coordinate { int x,y,z; }; //<-- integer based grid
HashSet<Coordinate> world; // <-- contains all the cubes

//width, height, and length represent how many cubes it spans
struct RectangularBox { Coordinate coord; int width,height,length; }

void Begin()
{
    List<RectangularBox> fewestBoxes = new List<RectangularBox>();
    while(world.Count > 0)
    {
         RectangularBox currentLargest = ExtractLargest();
         fewestBoxes.Add(currentLargest);
         world.RemoveRange(currentLargest.ContainedCubes());
    }
    //done; `fewestBoxes` contains the fewest rectangular boxes needed.
}

private RectangularBox ExtractLargest()
{
    RectangularBox largestBox = new RectangularBox();
    foreach (Coordinate origin in world)
    {
        RectangularBox box = FindMaximumSpan(origin);
        if (box.CalculateVolume() >= largestBox.CalculateVolume())
            largestBox = box;
    }
    return largestBox;
}

private RectangularBox FindMaximumSpan(Coordinate origin)
{
    int maxX, maxY,maxZ;
    while (world.Contains(origin.Offset(maxX, 0, 0))) maxX++;
    while (world.Contains(origin.Offset(0, maxY, 0))) maxY++;
    while (world.Contains(origin.Offset(0, 0, maxZ))) maxZ++;

    RectangularBox largestBox;
    for (int extentX = 0; extentX <= maxX; extentX++)
        for (int extentY = 0; extentY <= maxY; extentY++)
            for (int extentZ = 0; extentZ <= maxZ; extentZ++)
            {
                int lengthX = extentX + 1;
                int lengthY = extentY + 1;
                int lengthZ = extentZ + 1;
                if (BoxIsFilledWithCubes(origin, lengthX, lengthY, lengthZ))
                {
                    int totalVolume = lengthX * lengthY * lengthZ;
                    if (totalVolume >= largestBox.ComputeVolume())
                        largestBox = new RectangularBox(origin, lengthX, lengthY, lengthZ);
                }
                else
                    break;
            }
    return largestBox;
}

private bool BoxIsFilledWithCubes(Coordinate coord, 
    int lengthX, int lengthY, int lengthZ)
{
    for (int gX = 0; gX < lengthX; gX++)
        for (int gY = 0; gY < lengthY; gY++)
            for (int gZ = 0; gZ < lengthZ; gZ++)
                if (!world.Contains(coord.Offset(gX, gY, gZ)))
                    return false;
    return true;
}

基本的に、世界のすべてのブロックについて、最初に3つの正の次元(+ X、+ Y、+ Z)のそれぞれに存在する隣接ブロックの数を見つけます。そして、そのボリュームを塗りつぶし、ブロックが欠落していない最大の充填を確認します。


更新:これはゲームのレンダリングエンジン用であると示唆しているようだったので、はっきりさせておきたいのですが、これはゲームのレンダリングエンジン用ではありません。これはファイルコンバーター用です。GUIなし。


2
おそらくcodereview.stackexchange.comに適しています
Rotem

4
@Rotemおそらく、しかし、私は実際には自分のコードのレビューではなく、代替アルゴリズムを探しています。私は習慣の力としてコードを提供しました。
スミス氏

確かに、理にかなっています。
Rotem

アルゴリズムの質問は、コンピューターサイエンスなどのSEサイトに適しています...
バクリウ

また、メソッドを呼び出す頻度にも依存します。フレームごと、またはブロックが変更されたときにのみ呼び出す場合。これらのゲームは通常、チャンク(64x64x32ブロックなどの特定のサイズの長方形)を持ち、可能な限り値をキャッシュし、チャンクごとにのみ計算します。そして、これらの値は可視ブロックでのみ計算します。
the_lotus

回答:


6

という事実を利用することができます

 BoxIsFilledWithCubes(c,x,y,z)

trueを返す場合、BoxIsFilledWithCubes(c,x+1,y,z)座標範囲「(c、x、y、z)」内のすべてのキューブを再度チェックインする必要はありません。新しいx座標でこれらのキューブをチェックするだけで済み c.x + (x+1)ます。(y+1、またはも同じですz+1)。より一般的には、ボックスを2つの小さなボックスに分割することで(ボックスが両方ともキューブで満たされているか、どちらも満たされていないかはすでにわかっているかもしれません)、ここで分割統治手法を適用できます。中間結果をキャッシュするときの元のアプローチ。

これを行うにBoxIsFilledWithCubes(c,x,y,z)は、たとえば次のように再帰的に実装を開始します。

 bool BoxIsFilledWithCubes(coord,lx,ly,lz)
 {
     if(lx==0|| ly==0 || lz==0)
        return true;
     if(lx==1 && ly==1 && lz==1)
          return world.Contains(coord);
     if(lx>=ly && lx>=lz)  // if lx is the maximum of lx,ly,lz ....
         return BoxIsFilledWithCubes(coord,lx/2,ly,lz) 
             && BoxIsFilledWithCubes(coord.Offset(lx/2,0,0), lx-lx/2, ly, lz);
     else if(ly>=lz && ly>=lx)  
         // ... analogously when ly or lz is the maximum

 }

次に、メモ化ここで説明しているように)を使用しBoxIsFilledWithCubesて、同じパラメーターでへの繰り返し呼び出しを回避します。のworldようにコンテナに変更を適用する場合は、メモ化キャッシュをクリアする必要があることに注意してくださいworld.RemoveRange。それでも、これによりプログラムが高速になると思います。


5

ボックスのサイズのリーフノードaabbサイズでoctreeを構築します。octreeをトラバースしながら、安価にノードをマージできます。完全に満たされたノードはマージするのは簡単です(新しいボックス=親aabb)。一方、部分的に満たされたノードの場合、現在のアルゴリズムのバリアントを使用してマージ可能性をチェックできます。


2つのノードが完全に満たされているからといって、それらをマージする必要があるという意味ではありません。最大のボックスを見つけるためのステップではありません。それらをマージする場合、最大のボックスが見つかったら、後でそれらを再度分割する必要があります。このシナリオでoctreeがどのように役立つかはわかりません。
スミス氏2015

完全なノードは、8つの子すべてが1つの大きなボックスの一部になることができるという意味でマージできます。この大きなボックスは後で分割する必要はありませんが、結合される場合があります。階層を使用すると、多数のボックスをすばやくマージできます。完全に満たされたノードを使用すると、さらにテストすることなくレベルを上げることができます。
Wilbert、2015

1

あなたは、ように見える少なくともO(N ^ 2)で(参照ランダウの記号を使用するには、(ExtractLargestで、世界のすべてのボックスをループし、各ボックスのために、「)(開始」で、世界のすべてのボックスの上にあなたのループとして) )。したがって、10個の無関係なボックスを持つ世界は、5個の無関係なボックスを持つ世界の4倍の時間がかかります。

したがって、ExtractLargest()が見る必要があるボックスの数を制限する必要があります。これを行うには、あるタイプの空間検索を使用する必要があります。3Dで作業している場合、3D空間検索が必要になる場合があります。ただし、最初に2D空間検索を理解することから始めます。

次に、多くのボックスが互いに重なり合うかどうかを検討します。そうでない場合は、x、yのみをカバーする2D空間検索でループを削減できます。

Octree / quadtreeは1つのオプションですが、スペース分割には他にも多くのオプションがあります。

しかし、リスト(グリッド空間インデックス)の2次元配列を使用するだけで、ポイント(a、b)をカバーするすべてのボックスがarray [a、b] .listにある場合があります。しかし、なめらかに配列が大きくなりすぎるので、array [mod(a、100)、mob(b、100)]。listはどうでしょうか。 これはすべて、データがどのようなものかによって異なります

(グリッドソリューションが実際の生活で非常にうまく機能するのを見てきました。)

または、ボックスのサイズのリーフノードaabbサイズのoctreeでWilbertが言うことを実行しますが、後で空間検索の場合と同様に、ユーザーのマウスが指しているボックスなどを見つける必要があります。

このソフトウェアを機能させるだけなのか、それともより優れたプログラマーになる方法を理解しようとしているのか、したがって、学習よりも迅速な解決策に関心があるのか​​を判断する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.