Daha küçük kutulardan yapılabilecek en büyük kutuyu ayıklayan algoritmam çok yavaş


9

Her şeyin aynı boyutta küplerden oluştuğu ve tüm küplerin aynı türden olduğu küp tabanlı bir dünya (Minecraft, Trove veya Cube World gibi) düşünün .

Amaç, en az sayıda dikdörtgen kutu ile dünyayı temsil etmektir (küpleri birleştirerek, ancak dışbükey şekli koruyarak (yani, dikdörtgen kutu şekli). Algoritmam bunu başardı, ancak performansı amaçlanan amaç için çok yavaş. Daha hızlı algoritmalar var mı?

Algoritmamın sözde-C # kodu temelde:

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;
}

Esasen, dünyadaki her blok için, ilk olarak üç pozitif boyutun (+ X, + Y, + Z) her birinde kaç tane blok blok bulunduğunu bulur. Ve sonra bu hacmi sel doldurur ve herhangi bir blok eksik olmayan en büyük dolgu olanı kontrol eder.


Güncelleme: Bunun bir oyunun render motoru için olduğunu ima ettiğimden, açıklığa kavuşturmak istiyorum, bu bir oyunun render motoru için değil; bir dosya dönüştürücü içindir; GUI yok.


2
Belki de codereview.stackexchange.com için daha uygun
Rotem

4
@Rotem Belki, ama aslında kodumu bir inceleme yerine alternatif algoritmalar arıyorum. Kodumu bir alışkanlık gücü olarak verdim.
Bay Smith

Tabii, mantıklı.
Rotem

Algoritma soruları bilgisayar bilimi gibi SE sitelerine daha uygundur ...
Bakuriu

Ayrıca, yöntemi ne sıklıkta çağırdığınıza da bağlıdır. Her kareyi çağırırsanız veya yalnızca bir blok değiştiğinde. Bu oyunlarda genellikle yığınlar (örn. 64x64x32 bloklar gibi belirli boyutlu dikdörtgenler), önbellek değerleri olabildiğince önbelleğe alınır ve yalnızca yığın başına hesaplanır. Ve sadece görünür bloklarda bu değerleri hesaplayın.
the_lotus

Yanıtlar:


6

Sen ne zaman kullanabilirsiniz gerçeği

 BoxIsFilledWithCubes(c,x,y,z)

true değerini döndürürse, BoxIsFilledWithCubes(c,x+1,y,z)"(c, x, y, z)" koordinat aralığındaki tüm küpleri tekrar kontrol etmek gerekmez . Bu küpleri yalnızca yeni x koordinatıyla kontrol etmeniz gerekir c.x + (x+1). (Aynısı y+1veya için de geçerlidir z+1). Daha genel olarak, bir kutuyu iki küçük kutuya bölerek (her ikisinin de küple dolu olup olmadığını veya her ikisinin de doldurulmadığını zaten biliyor olabilirsiniz), burada bölme ve fethetme tekniğini uygulayabilirsiniz. ara sonuçları önbelleğe aldığınızda orijinal yaklaşım.

Bunu yapmak için, BoxIsFilledWithCubes(c,x,y,z)tekrarlı olarak uygulamaya başlıyorsunuz , örneğin:

 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

 }

ve aynı parametrelerle tekrarlanan çağrılardan kaçınmak için not ( burada açıklandığı gibi ) kullanın . Kapsayıcınıza bir değişiklik uyguladığınızda, not önbelleğini temizlemeniz gerekeceğini unutmayın . Yine de sanırım bu programınızı daha hızlı hale getirecektir.BoxIsFilledWithCubesworldworld.RemoveRange


5

Bir yukarı kurmak Octree kutunuzun boyutta bir yaprak düğüm aabb boyutu ile. Oktree'yi dolaşırken düğümleri ucuza birleştirebilirsiniz. Tamamen doldurulmuş düğümlerin birleştirilmesi önemsizdir (yeni kutu = ebeveyn aabb), kısmen doldurulmuş düğümler için, birleştirme yeteneğini kontrol etmek için mevcut algoritmanızın bir varyantını kullanabilirsiniz.


İki düğümün tamamen doldurulmuş olması, birleştirilmeleri gerektiği anlamına gelmez; en büyük kutuyu bulma yolunda bir adım değil; bunları birleştirirseniz, en büyük kutu bulunduğunda bunları daha sonra tekrar bölmeniz gerekecektir. Bir octree'nin bu senaryoda nasıl yardımcı olduğunu görmüyorum.
Bay Smith

Tam düğümler, 8 çocuğun hepsinin tek bir büyük kutunun parçası olabileceği anlamında birleştirilebilir. Bu büyük kutunun daha sonra bölünmesi gerekmeyecek, ancak birleştirilebilir. Hiyerarşi, birçok kutuyu hızlı bir şekilde birleştirmenize izin verir, tamamen doldurulmuş düğümler, daha fazla test yapmadan bir seviyeye çıkmanıza izin verir.
Wilbert

1

Sen görünmektedir az O'da (n ^ 2) (bkz büyük Ç notasyonu ) ExtractLargest içinde dünyadaki tüm kutuların üzerinde, daha sonra her bir kutu için, sen döngü “) (Başlat” dünyadaki tüm kutuların üzerinde döngü olarak ( ). Dolayısıyla, 10 ilişkisiz kutunun olduğu bir dünya, 5 ilişkisiz kutunun olduğu bir dünyadan 4 kat daha uzun sürecektir.

Bu nedenle, ExtractLargest () 'in bakması gereken kutu sayısını sınırlamanız gerekir, bunu yapmak için bir tür uzamsal arama kullanmanız gerekir , 3B olarak çalışırken, 3B bir uzamsal aramaya ihtiyacınız olabilir. Ancak ilk olarak 2d uzamsal aramayı anlayarak başlayın.

O zaman birbirinizin üzerinde çok sayıda kutunuz olacaksa, sadece x'i kapsayan bir 2d uzamsal arama yapmazsanız, döngünüzü kesmek için yeterli olabilir.

Octree / quadtree bir seçenektir, ancak alan bölümleme için başka birçok seçenek vardır ,

Ancak (a, b) noktasını kapsayan tüm kutuların [a, b] .list listesinde olduğu 2 boyutlu bir liste dizisini ( Izgara uzamsal dizini ) kullanabilirsiniz. Ama en lickly bu çok büyük bir dizi yol açacaktır, peki dizi [mod (a, 100), mafya (b, 100)]. Liste? Bu, verilerinizin nasıl olduğuna bağlıdır .

(Izgara çözümünün gerçek hayatta çok iyi çalıştığını gördüm.)

Veya Wilbert'in söylediklerini, kutunuzun boyutunda bir yaprak düğümü aabb boyutuna sahip bir oktree ile yapın, ancak daha sonra kullanıcının faresinin vb.

( Bu yazılımın çalışmasını sağlamaya çalıştığınıza mı, yoksa daha iyi bir programcı olmayı nasıl anlayacağınıza ve dolayısıyla hızlı bir çözümden sonra öğrenme ile daha fazla ilgilenmeye mi çalıştığınıza karar vermelisiniz. )

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.