Apakah ada cara untuk meningkatkan efisiensi pemeriksaan benturan pada suatu sistem objek n?

9

Saya membuat game yang terdiri dari banyak objek pada layar, salah satunya adalah pemain. Saya perlu tahu objek mana yang bertabrakan setiap iterasi.

Saya membuat sesuatu seperti ini:

for (o in objects)
{
   o.stuff();
   for (other in objects)
      if (collision(o, other))
          doStuff();

   bla.draw();
}

Ini memiliki O (n ^ 2), yang menurut saya buruk. Bagaimana saya melakukan ini dengan lebih efisien, mungkinkah? Saya melakukan ini dalam Javascript, dan n biasanya akan lebih rendah dari 30, apakah ini akan menjadi masalah jika ini tetap sama?

jcora
sumber
3
Sudahkah Anda mencoba menjalankan kode untuk melihat kinerjanya?
thedaian
Tidak, saya belum, saya hanya menganggap itu buruk karena O (n ^ 2).
jcora
1
Hanya 30 benda? Saya akan merekomendasikan partisi spasial, tetapi tidak akan membuahkan hasil dengan hanya 30 objek. Ada beberapa optimasi kecil yang ditunjukkan orang lain, tetapi mereka semua adalah optimasi kecil pada skala yang Anda bicarakan.
John McDonald

Jawaban:

16

Dengan hanya 30 objek maks, Anda tidak perlu banyak optimasi sama sekali selain untuk tidak memeriksa dua pasangan yang sama satu sama lain lebih dari sekali per frame. Yang akan dicakup contoh kode di bawah ini. Tetapi jika Anda menarik dalam berbagai optimisasi yang akan digunakan mesin fisika, lanjutkan membaca sisa posting ini.

Yang Anda perlukan adalah implementasi partisi spasial , seperti Octree (untuk game 3D) atau Quadtree (untuk game 2D). Ini mempartisi dunia menjadi sub-bagian, dan kemudian masing-masing sub-bagian dipartisi lebih lanjut dalam manor yang sama, sampai mereka dibagi menjadi ukuran minimum. Ini memungkinkan Anda untuk dengan cepat memeriksa objek mana yang berada di wilayah yang sama dengan yang lain di dunia, yang membatasi jumlah tabrakan yang harus Anda periksa.

Selain partisi spasial saya akan merekomendasikan membuat AABB ( Axis-aligned box ) untuk masing-masing objek fisika Anda. Ini memungkinkan Anda untuk memeriksa AABB dari satu objek terhadap yang lain, yang jauh lebih cepat daripada cek per-poli rinci antara objek.

Ini dapat diambil selangkah lebih jauh untuk objek fisika yang rumit atau besar, di mana Anda dapat membagi mesh fisika itu sendiri, memberikan masing-masing sub-bentuk AABB sendiri yang dapat Anda periksa hanya jika AABB dua objek saling tumpang tindih.

Sebagian besar mesin fisika akan menonaktifkan simulasi fisika aktif pada tubuh fisika begitu mereka beristirahat. Ketika sebuah tubuh fisika dinonaktifkan, ia hanya perlu memeriksa tabrakan terhadap AABB setiap frame, dan jika ada sesuatu yang bertabrakan dengan AABB maka ia akan mengaktifkan kembali dan melakukan pemeriksaan tabrakan yang lebih granular. Ini menjaga waktu simulasi turun.

Juga, banyak mesin fisika menggunakan 'pulau simulasi', yang merupakan tempat sekelompok badan fisika yang berdekatan dikelompokkan bersama. Jika semua yang ada di pulau simulasi diam maka pulau simulasi itu sendiri akan dinonaktifkan. Manfaat dari pulau simulasi adalah bahwa semua benda di dalamnya dapat berhenti memeriksa tabrakan begitu pulau tidak aktif, dan satu-satunya memeriksa setiap frame adalah untuk melihat apakah ada sesuatu yang memasuki AABB pulau. Hanya sekali sesuatu memasuki AABB pulau maka masing-masing badan di dalam pulau perlu memeriksa tabrakan. Pulau simulasi juga mengaktifkan kembali jika benda di dalamnya mulai bergerak sendiri. Jika suatu benda bergerak cukup jauh dari pusat kelompok, ia dikeluarkan dari pulau.

Pada akhirnya Anda dibiarkan dengan sesuatu seperti ini (dalam pseudo-code):

// Go through each leaf node in the octree. This could be more efficient
// by keeping a list of leaf nodes with objects in it.
for ( node in octreeLeafNodes )
{
    // We only need to check for collision if more than one object
    // or island is in the bounds of this octree node.
    if ( node.numAABBsInBounds > 1)
    {
        for ( int i = 0; i < AABBNodes.size(); ++i )
        {
           // Using i+1 here allows us to skip duplicate checks between AABBS
           // e.g (If there are 5 bodies, and i = 0, we only check i against
           //      indexes 1,2,3,4. Once i = 1, we only check i against indexes
           //      2,3,4)
           for ( int j = i + 1; j < AABBNodes.size(); ++j )
           {
               if ( AABBOverlaps( AABBNodes[i], AABBNodes[j] ) )
               {
                   // If the AABB we checked against was a simulation island
                   // then we now check against the nodes in the simulation island

                   // Once you find overlaps between two actual object AABBs
                   // you can now check sub-nodes with each object, if you went
                   // that far in optimizing physics meshes.
               {
           }
        }
    }
}

Saya juga akan merekomendasikan tidak memiliki begitu banyak loop dalam loop seperti ini, sampel di atas hanya jadi Anda punya ide, saya akan memecahnya menjadi beberapa fungsi yang memberi Anda fungsi yang sama seperti sesuatu seperti yang ditunjukkan di atas.

Juga, pastikan untuk tidak mengubah wadah AABBNodes saat mengulanginya, karena itu bisa berarti cek tabrakan yang terlewat. Ini mungkin terdengar seperti akal sehat, tetapi Anda akan terkejut betapa mudahnya untuk bereaksi terhadap benturan menyebabkan perubahan yang tidak Anda harapkan. Misalnya jika tabrakan menyebabkan salah satu objek bertabrakan untuk mengubah posisi cukup untuk menghapusnya dari AABB dari node Octree yang Anda periksa maka itu dapat mengubah wadah itu. Untuk mengatasi ini, saya sarankan menyimpan daftar semua peristiwa tabrakan yang terjadi selama pemeriksaan, dan kemudian setelah semua pemeriksaan selesai dijalankan melalui daftar dan mengirimkan semua peristiwa tabrakan.

Nic Foster
sumber
4
Jawaban yang sangat konsisten dengan persiapan teknis yang bagus dan bermanfaat untuk membuka pikiran pembaca terhadap metode yang ada. +1
Valkea
Bagaimana jika saya perlu menghapus objek bertabrakan? Bisakah saya mengganti wadah? Maksud saya mengeluarkannya dari wadah karena saya tidak membutuhkan objek lagi karena "dihancurkan". Saya perlu satu loop lagi untuk menjalankan melalui peristiwa tabrakan jika saya tidak menghapusnya selama deteksi tabrakan.
newguy
Menghapus objek bertabrakan baik-baik saja tetapi saya akan merekomendasikan menunggu untuk melakukannya sampai setelah tabrakan telah dilakukan di seluruh simulasi. Biasanya Anda hanya menandai objek yang perlu dihapus, atau membuat daftar objek yang akan dihapus, dan kemudian setelah simulasi tabrakan selesai Anda menerapkan perubahan itu.
Nic Foster
4

Contoh Anda menguji setiap pasangan objek beberapa kali.

Mari kita ambil contoh yang sangat sederhana dengan array yang berisi 0,1,2,3

Dengan kode Anda, Anda mendapatkan ini:

  • Pada loop 0 Anda menguji terhadap 1, 2 dan 3
  • Pada loop 1 Anda menguji terhadap 0, 2 dan 3 ===> (0-1 sudah diuji)
  • Pada loop 2 Anda menguji terhadap 0, 1 dan 3 ===> (0-2 / 1-2 sudah diuji)
  • Pada loop 3 Anda menguji 0, 1 dan 2 ===> (0-3 / 1-3 / 2-3 sudah diuji)

Sekarang mari kita lihat kode berikut:

for(i=0;i<=objects.length;i++)
{
    objects[i].stuff();

    for(j=i+1;j<=objects.length;j++)
    {
        if (collision(objects[i], objects[j]))
        doStuff();
    }

    bla.draw();
}

Jika kami menggunakan array yang mengandung 0,1,2,3 sekali lagi, kami memiliki perilaku berikut:

  • Pada loop 0 Anda menguji terhadap 1, 2, 3
  • Pada loop 1 Anda menguji terhadap 2, 3
  • Pada loop 2 Anda menguji terhadap 3
  • Pada loop 3 Anda menguji apa-apa

Dengan algoritma kedua kami telah mendapat 6 tes tabrakan, sedangkan yang sebelumnya meminta 12 tes tabrakan.

Valkea
sumber
Algoritma ini membuat N(N-1)/2perbandingan yang masih O (N ^ 2) kinerja.
Kai
1
Nah dengan 30 objek seperti yang diminta itu berarti 465 tes tabrakan terhadap 870 ... mungkin mirip dari sudut pandang Anda, tetapi tidak dari milik saya. Lebih jauh lagi, solusi yang ditawarkan dalam jawaban lain adalah algoritma yang persis sama :)
Valkea
1
@ Valkea: Yah, sebagian dari itu. :)
Nic Foster
@NicFoster: ya Anda benar;) Saya berbicara secara ketat tentang uji tabrakan antara objek yang dipilih, bukan tentang bagian partisi dari algoritma yang jelas merupakan tambahan yang sangat berharga yang saya bahkan tidak berpikir untuk menambahkan contoh saya ketika Saya sedang menulisnya.
Valkea
Apakah ini disebut amortisasi? Bagaimanapun, terima kasih!
jcora
3

Rancang algoritme sesuai kebutuhan Anda, tetapi pertahankan detail implemetasinya. Bahkan dalam Javascript, konsep OOP dasar berlaku.

Karena N =~ 30, O(N*N)bukan masalah, dan pencarian linier Anda kemungkinan akan sama cepatnya dengan segala alternatif di luar sana. Tetapi Anda tidak ingin asumsi hard-code menjadi kode Anda. Dalam pseudocode, Anda akan memiliki antarmuka

interface itemContainer { 
    add(BoundingBox);
    remove(BoundingBox);
    BoundingBox[] getIntersections();
}

Itu menjelaskan apa yang dapat dilakukan daftar item Anda. Kemudian Anda bisa menulis kelas ArrayContainer yang mengimplementasikan antarmuka ini. Dalam Javascript, kode akan terlihat seperti ini:

function ArrayContainer() { ... } // this uses an array to store my objects
ArrayContainer.prototype.add = function(box) { ... };
ArrayContainer.prototype.remove = function(box) { ... };
ArrayContainer.prototype.getIntersections = function() { ... };

function QuadTreeContainer { ... } // this uses a quadtree to store my objects
... and implement in the add/remove/getIntersections for QuadTreeContainer too

Dan di sini adalah contoh kode yang membuat 300 kotak pembatas dan mendapatkan semua persimpangan. Jika Anda telah mengimplementasikan ArrayContainer dan QuadTreeContainer dengan benar, satu-satunya hal yang perlu Anda ubah dalam kode Anda adalah berubah var allMyObjects = new ArrayContainer()menjadi var allMyObjects = QuadTreeContainer().

var r = Math.random;
var allMyObjects = new ArrayContainer();
for(var i=0; i<300; i++)
    allMyObjects.add(new BoundingBox(r(), r()));
var intersections = allMyObjects.getIntersections();

Saya melanjutkan dan membuat implementasi untuk standar ArrayContainer di sini:

http://jsfiddle.net/SKkN5/1/

Jimmy
sumber
Catatan: Jawaban ini dimotivasi oleh keluhan Bane bahwa basis kode-nya menjadi terlalu besar, berantakan, dan sulit dikelola. Meskipun itu tidak menambah banyak diskusi tentang penggunaan Array vs Pohon, saya harap ini jawaban yang relevan karena bagaimana secara khusus dia bisa mengatur kode lebih baik.
Jimmy
2

Anda juga harus mempertimbangkan jenis-jenis objek yang dapat bertabrakan dengan wajar.

Misalnya pemain mungkin perlu diperiksa untuk tabrakan dengan segala sesuatu kecuali peluru sendiri. Namun musuh mungkin hanya perlu memeriksa terhadap peluru pemain. Peluru hampir pasti tidak perlu saling bertabrakan.

Untuk menerapkan ini secara efisien Anda mungkin ingin menyimpan daftar objek yang terpisah, satu per jenis objek.

Adam
sumber