Jika ini adalah pertama kalinya Anda pada pertanyaan ini, saya sarankan membaca bagian pra-pembaruan di bawah ini terlebih dahulu, lalu bagian ini. Berikut adalah sintesis masalah:
Pada dasarnya, saya memiliki mesin pendeteksi tabrakan dan resolusi dengan sistem partisi spasial grid di mana urutan-tabrakan dan kelompok tabrakan penting. Satu tubuh pada suatu waktu harus bergerak, kemudian mendeteksi tabrakan, lalu menyelesaikan tabrakan. Jika saya memindahkan semua benda sekaligus, maka menghasilkan kemungkinan pasangan tabrakan, itu jelas lebih cepat, tetapi resolusi pecah karena urutan tabrakan tidak dihormati. Jika saya menggerakkan satu tubuh sekali waktu, saya dipaksa untuk mendapatkan tubuh untuk memeriksa tabrakan, dan itu menjadi masalah. Letakkan kelompok dalam campuran, dan Anda dapat membayangkan mengapa itu menjadi sangat lambat sangat cepat dengan banyak tubuh.
Pembaruan: Saya telah bekerja sangat keras dalam hal ini, tetapi tidak dapat mengelola untuk mengoptimalkan apa pun.
Saya juga menemukan masalah besar : mesin saya tergantung pesanan.
Saya mencoba implementasi generasi tabrakan yang unik , yang pasti mempercepat semuanya, tetapi mematahkan urutan tabrakan .
Biarkan saya jelaskan:
dalam desain asli saya (tidak menghasilkan pasangan), ini terjadi:
- satu tubuh bergerak
- setelah bergerak, ia menyegarkan sel-selnya dan membuat tubuh yang bertabrakan dengannya
- jika tumpang tindih dengan benda yang harus diatasi, selesaikan tabrakan
ini berarti bahwa jika suatu benda bergerak, dan mengenai dinding (atau benda lain mana pun), hanya benda yang telah bergerak yang akan menyelesaikan tabrakannya dan benda lainnya tidak akan terpengaruh.
Ini adalah perilaku yang saya inginkan .
Saya mengerti itu tidak umum untuk mesin fisika, tetapi memiliki banyak keuntungan untuk game bergaya retro .
dalam desain kisi biasa (menghasilkan pasangan unik), ini terjadi:
- semua tubuh bergerak
- setelah semua tubuh bergerak, segarkan semua sel
- menghasilkan pasangan tabrakan yang unik
- untuk setiap pasangan, menangani deteksi dan resolusi tabrakan
dalam hal ini gerakan simultan bisa mengakibatkan dua tubuh tumpang tindih, dan mereka akan menyelesaikan pada saat yang sama - ini secara efektif membuat tubuh "saling mendorong", dan merusak stabilitas tabrakan dengan banyak benda
Perilaku ini umum untuk mesin fisika, tetapi tidak dapat diterima dalam kasus saya .
Saya juga menemukan masalah lain, yang utama (bahkan jika itu tidak mungkin terjadi dalam situasi dunia nyata):
- pertimbangkan badan kelompok A, B dan W
- A bertabrakan dan memutuskan melawan W dan A
- B bertabrakan dan memutuskan melawan W dan B
- A tidak melakukan apa pun terhadap B
- B tidak melakukan apa pun terhadap A
mungkin ada situasi di mana banyak tubuh A dan tubuh B menempati sel yang sama - dalam hal ini, ada banyak iterasi yang tidak perlu antara tubuh yang tidak boleh bereaksi satu sama lain (atau hanya mendeteksi tabrakan tetapi tidak menyelesaikannya) .
Untuk 100 mayat yang menempati sel yang sama, ini 100 ^ 100 iterasi! Ini terjadi karena pasangan unik tidak dihasilkan - tetapi saya tidak dapat menghasilkan pasangan unik , jika tidak saya akan mendapatkan perilaku yang tidak saya inginkan.
Apakah ada cara untuk mengoptimalkan jenis mesin tabrakan ini?
Ini adalah pedoman yang harus dihormati:
Urutan tabrakan sangat penting!
- Tubuh harus bergerak satu per satu , kemudian memeriksa tabrakan satu per satu , dan menyelesaikan setelah gerakan satu per satu .
Badan harus memiliki 3 bit grup
- Grup : grup yang dimiliki tubuh
- GroupsToCheck : grup yang harus dideteksi tubuh terhadap tabrakan
- GroupsNoResolve : mengelompokkan tubuh yang tidak boleh menyelesaikan tabrakan
- Mungkin ada situasi di mana saya hanya ingin tabrakan terdeteksi tetapi tidak diselesaikan
Pra-perbarui:
Kata Pengantar : Saya sadar bahwa mengoptimalkan hambatan ini bukanlah suatu keharusan - mesin sudah sangat cepat. Saya, bagaimanapun, untuk tujuan yang menyenangkan dan mendidik, akan senang menemukan cara untuk membuat mesin lebih cepat.
Saya membuat mesin pendeteksi / respons tabrakan C ++ 2D untuk keperluan umum, dengan penekanan pada fleksibilitas dan kecepatan.
Berikut diagram arsitekturnya yang sangat mendasar:
Pada dasarnya, kelas utamanya adalah World
, yang memiliki (mengelola memori) dari a ResolverBase*
, a SpatialBase*
dan a vector<Body*>
.
SpatialBase
adalah kelas virtual murni yang berurusan dengan deteksi tabrakan fase luas.
ResolverBase
adalah kelas virtual murni yang berurusan dengan resolusi tabrakan.
Tubuh berkomunikasi World::SpatialBase*
dengan SpatialInfo
benda - benda, yang dimiliki oleh tubuh itu sendiri.
Saat ini ada satu kelas spasial:, yang merupakan Grid : SpatialBase
grid 2D tetap dasar. Ini memiliki kelas info sendiri GridInfo : SpatialInfo
,.
Begini tampilannya arsitekturnya:
The Grid
kelas memiliki array 2D Cell*
. The Cell
kelas berisi kumpulan (tidak dimiliki) Body*
: a vector<Body*>
yang berisi semua mayat yang ada di sel.
GridInfo
benda juga mengandung petunjuk yang tidak memiliki sel-sel di mana tubuh berada.
Seperti yang saya katakan sebelumnya, mesin didasarkan pada kelompok.
Body::getGroups()
mengembalikan astd::bitset
dari semua grup yang menjadi bagian tubuh.Body::getGroupsToCheck()
mengembalikan astd::bitset
dari semua grup yang harus diperiksa oleh tubuh terhadap benturan
Tubuh dapat menempati lebih dari satu sel. GridInfo selalu menyimpan pointer yang tidak memiliki ke sel yang ditempati.
Setelah satu tubuh bergerak, deteksi tabrakan terjadi. Saya berasumsi bahwa semua benda adalah kotak pembatas sumbu-sejajar.
Cara kerja deteksi tabrakan fase lebar:
Bagian 1: pembaruan info spasial
Untuk masing-masing Body
body
:
- Sel yang ditempati paling kiri atas dan sel yang ditempati paling kanan bawah dihitung.
- Jika mereka berbeda dari sel-sel sebelumnya,
body.gridInfo.cells
dibersihkan, dan diisi dengan semua sel yang ditempati tubuh (2D untuk loop dari sel paling kiri atas ke sel paling kanan bawah).
body
sekarang dijamin untuk mengetahui sel apa yang ditempatinya.
Bagian 2: cek tabrakan yang sebenarnya
Untuk masing-masing Body
body
:
body.gridInfo.handleCollisions
disebut:
void GridInfo::handleCollisions(float mFrameTime)
{
static int paint{-1};
++paint;
for(const auto& c : cells)
for(const auto& b : c->getBodies())
{
if(b->paint == paint) continue;
base.handleCollision(mFrameTime, b);
b->paint = paint;
}
}
void Body::handleCollision(float mFrameTime, Body* mBody)
{
if(mBody == this || !mustCheck(*mBody) || !shape.isOverlapping(mBody->getShape())) return;
auto intersection(getMinIntersection(shape, mBody->getShape()));
onDetection({*mBody, mFrameTime, mBody->getUserData(), intersection});
mBody->onDetection({*this, mFrameTime, userData, -intersection});
if(!resolve || mustIgnoreResolution(*mBody)) return;
bodiesToResolve.push_back(mBody);
}
Tabrakan kemudian diselesaikan untuk setiap tubuh di
bodiesToResolve
.Itu dia.
Jadi, saya sudah mencoba untuk mengoptimalkan deteksi tabrakan fase luas ini cukup lama sekarang. Setiap kali saya mencoba sesuatu yang lain dari arsitektur / setup saat ini, sesuatu tidak berjalan sesuai rencana atau saya membuat asumsi tentang simulasi yang kemudian terbukti salah.
Pertanyaan saya adalah: bagaimana saya bisa mengoptimalkan fase luas dari mesin tabrakan saya ?
Apakah ada beberapa optimasi C ++ ajaib yang dapat diterapkan di sini?
Dapatkah arsitektur dirancang ulang untuk memungkinkan kinerja yang lebih baik?
- Implementasi aktual: SSVSCollsion
- Body.h , Body.cpp
- World.h , World.cpp
- Grid.h , Grid.cpp
- Cell.h , Cell.cpp
- GridInfo.h , GridInfo.cpp
Output callgrind untuk versi terbaru: http://txtup.co/rLJgz
sumber
getBodiesToCheck()
dipanggil 5462334 kali, dan mengambil 35,1% dari seluruh waktu pembuatan profil (Waktu akses baca instruksi)Jawaban:
getBodiesToCheck()
Mungkin ada dua masalah dengan
getBodiesToCheck()
fungsi tersebut; pertama:Bagian ini O (n 2 ) bukan?
Daripada memeriksa untuk melihat apakah tubuh sudah ada dalam daftar, gunakan lukisan sebagai gantinya.
Anda mendereferensi pointer pada fase kumpulkan, tetapi Anda tetap akan melakukan redereferensi pada fase uji jadi jika Anda memiliki cukup L1 itu bukan masalah besar. Anda dapat meningkatkan kinerja dengan menambahkan petunjuk pra-pengambilan ke kompiler juga misalnya
__builtin_prefetch
, meskipun itu lebih mudah denganfor(int i=q->length; i-->0; )
loop klasik dan semacamnya.Itu tweak sederhana, tetapi pemikiran kedua saya adalah bahwa mungkin ada cara yang lebih cepat untuk mengatur ini:
Namun, Anda dapat beralih menggunakan bitmap , dan menghindari seluruh
bodiesToCheck
vektor. Inilah pendekatannya:Anda sudah menggunakan kunci integer untuk tubuh, tetapi kemudian mencari mereka di peta dan hal-hal dan menyimpan sekitar daftar mereka. Anda dapat pindah ke pengalokasi slot, yang pada dasarnya hanya sebuah array atau vektor. Misalnya:
Apa artinya ini adalah bahwa semua hal yang diperlukan untuk melakukan tabrakan sebenarnya adalah dalam memori yang ramah-cache linear, dan Anda hanya pergi ke bit implementasi khusus dan melampirkannya ke salah satu slot ini jika ada beberapa yang perlu.
Untuk melacak alokasi dalam vektor benda ini, Anda dapat menggunakan array bilangan bulat sebagai bitmap dan menggunakan bit twiddling atau lainnya
__builtin_ffs
. Ini sangat efisien untuk berpindah ke slot yang saat ini ditempati, atau menemukan slot kosong di dalam array. Anda bahkan dapat memadatkan array kadang-kadang jika tumbuh terlalu besar dan kemudian banyak ditandai dihapus, dengan menggerakkan mereka di ujung untuk mengisi celah.hanya periksa setiap tabrakan sekali
Jika Anda sudah memeriksa jika sebuah bertabrakan dengan b , Anda tidak perlu untuk memeriksa apakah b bertabrakan dengan sebuah juga.
Ini mengikuti dari menggunakan bilangan bulat id bahwa Anda menghindari pemeriksaan ini dengan pernyataan if sederhana. Jika id dari tabrakan potensial kurang dari atau sama dengan id saat ini sedang diperiksa, itu dapat dilewati! Dengan cara ini, Anda hanya akan memeriksa setiap kemungkinan pasangan satu kali; itu akan lebih dari setengah jumlah cek tabrakan.
hormati urutan tabrakan
Daripada mengevaluasi tabrakan segera setelah pasangan ditemukan, hitung jarak untuk memukul dan menyimpannya dalam tumpukan biner . Tumpukan ini adalah bagaimana Anda biasanya melakukan antrian prioritas dalam pencarian jalur, sehingga sangat berguna kode utilitas.
Tandai setiap node dengan nomor urut, sehingga Anda dapat mengatakan:
Tentunya setelah Anda mengumpulkan semua tabrakan, Anda mulai muncul dari antrian prioritas, paling cepat terlebih dahulu. Jadi yang pertama Anda dapatkan adalah A 10 hits C 12 pada 3. Anda menambah nomor urut setiap objek ( 10 bit), mengevaluasi tabrakan, dan menghitung jalur baru mereka, dan menyimpan tabrakan baru mereka dalam antrian yang sama. Tabrakan baru adalah A 11 hit B 12 at 7. Antrian sekarang memiliki:
Kemudian Anda pop dari antrian prioritas dan yang A 10 B hit 12 di 6. Tapi Anda melihat bahwa A 10 adalah basi ; A saat ini di 11. Jadi Anda dapat membuang tabrakan ini.
Penting untuk tidak repot-repot mencoba menghapus semua tabrakan basi dari pohon; menghapus dari tumpukan itu mahal. Buang saja ketika Anda meletuskannya.
grid
Anda harus mempertimbangkan menggunakan quadtree sebagai gantinya. Ini adalah struktur data yang sangat mudah untuk diterapkan. Seringkali Anda melihat implementasi yang menyimpan poin tetapi saya lebih suka menyimpan rect, dan menyimpan elemen dalam node yang berisi itu. Ini berarti bahwa untuk memeriksa tabrakan, Anda hanya perlu mengulangi semua badan, dan, untuk masing-masing, memeriksanya terhadap benda-benda di simpul quad-tree yang sama (menggunakan trik penyortiran yang diuraikan di atas) dan semua yang ada di simpul quad-tree induk. Quad-tree itu sendiri adalah daftar kemungkinan tabrakan.
Ini Quadtree sederhana:
Kami menyimpan objek bergerak secara terpisah karena kami tidak perlu memeriksa apakah objek statis akan bertabrakan dengan apa pun.
Kami memodelkan semua objek sebagai kotak pembatas sumbu-selaras (AABB) dan kami menempatkannya di QuadTreeNode terkecil yang berisi mereka. Ketika QuadTreeNode banyak anak, Anda dapat membaginya lebih lanjut (jika benda-benda itu mendistribusikan diri ke anak-anak dengan baik).
Setiap permainan mencentang, Anda harus berulang ke quadtree dan menghitung gerakan - dan tabrakan - dari setiap objek bergerak. Itu harus diperiksa untuk tabrakan dengan:
Ini akan menghasilkan semua kemungkinan tabrakan, tidak berurutan. Anda kemudian melakukan gerakan. Anda harus memprioritaskan gerakan ini berdasarkan jarak dan 'siapa yang bergerak terlebih dahulu' (yang merupakan persyaratan khusus Anda), dan melaksanakannya dalam urutan itu. Gunakan heap untuk ini.
Anda dapat mengoptimalkan templat quadtree ini; Anda tidak perlu benar-benar menyimpan batas dan titik tengah; itu sepenuhnya turunan saat Anda berjalan di pohon. Anda tidak perlu memeriksa apakah model berada dalam batas, hanya periksa sisi mana yang merupakan titik pusat (tes "sumbu pemisahan").
Untuk memodelkan hal-hal yang terbang cepat seperti proyektil, alih-alih memindahkannya setiap langkah atau memiliki daftar 'peluru' terpisah yang selalu Anda periksa, cukup letakkan mereka di quadtree dengan persegi penerbangan mereka untuk beberapa langkah permainan. Ini berarti bahwa mereka bergerak di quadtree jauh lebih jarang, tetapi Anda tidak memeriksa peluru terhadap dinding yang jauh, jadi itu tradeoff yang bagus.
Objek statis besar harus dipecah menjadi bagian-bagian komponen; sebuah kubus besar harus menyimpan masing-masing wajah secara terpisah, misalnya.
sumber
Saya yakin Anda hanya memiliki satu ton cache yang meleset saat iterasi di atas tubuh. Apakah Anda menggabungkan semua tubuh Anda bersama-sama menggunakan skema desain berorientasi data? Dengan broadphase N ^ 2 saya dapat mensimulasikan ratusan dan ratusan , sambil merekam dengan fraps, tubuh tanpa tetes framerate ke daerah bawah (kurang dari 60), dan ini semua tanpa pengalokasi kustom. Bayangkan saja apa yang bisa dilakukan dengan penggunaan cache yang tepat.
Petunjuknya ada di sini:
Ini segera menimbulkan bendera merah besar. Apakah Anda mengalokasikan badan-badan ini dengan panggilan baru yang mentah? Apakah ada pengalokasi khusus yang digunakan? Yang paling penting adalah Anda memiliki semua tubuh Anda dalam susunan besar di mana Anda melintasi secara linier . Jika melintasi memori secara linear bukanlah sesuatu yang Anda rasa dapat Anda terapkan, pertimbangkan untuk menggunakan daftar yang terhubung secara intrinsif.
Selain itu Anda tampaknya menggunakan std :: map. Apakah Anda tahu bagaimana memori di dalam std :: map dialokasikan? Anda akan memiliki kompleksitas O (lg (N)) untuk setiap kueri peta, dan ini kemungkinan dapat ditingkatkan menjadi O (1) dengan tabel hash. Di atas semua ini, memori yang dialokasikan oleh std :: map juga akan merusak cache Anda.
Solusi saya adalah dengan menggunakan tabel hash intrusi di tempat std :: map. Sebuah contoh yang baik dari daftar yang saling terkait dan tabel hash yang mengganggu secara intrinsik ada di basis Patrick Wyatt dalam proyek coho-nya: https://github.com/webcoyote/coho
Jadi singkatnya, Anda mungkin perlu membuat beberapa alat khusus untuk diri sendiri, yaitu pengalokasi dan beberapa wadah yang mengganggu. Ini adalah yang terbaik yang bisa saya lakukan tanpa membuat profil kode sendiri.
sumber
new
ketika mendorong tubuh kegetBodiesToCheck
vektor - maksud Anda ini terjadi secara internal? Apakah ada cara untuk mencegah itu sementara masih memiliki koleksi tubuh berukuran dinamis?std::map
bukan hambatan - Saya juga ingat mencobadense_hash_set
dan tidak mendapatkan kinerja apa pun.getBodiesToCheck
panggilan per bingkai. Saya menduga pembersihan / dorongan yang konstan dalam vektor adalah hambatan dari fungsi itu sendiri. Thecontains
Metode ini juga merupakan bagian dari perlambatan, tapi karenabodiesToCheck
tidak pernah memiliki lebih dari 8-10 mayat di dalamnya, itu harus yang lambatKurangi jumlah mayat untuk memeriksa setiap frame:
Hanya periksa badan yang benar-benar bisa bergerak. Objek statis hanya perlu ditugaskan ke sel tumbukan Anda sekali setelah dibuat. Sekarang hanya periksa tabrakan untuk grup yang berisi setidaknya satu objek dinamis. Ini harus mengurangi jumlah cek setiap frame.
Gunakan quadtree. Lihat jawaban terperinci saya di sini
Hapus semua alokasi dari kode fisika Anda. Anda mungkin ingin menggunakan profiler untuk ini. Tapi saya hanya menganalisis alokasi memori dalam C #, jadi saya tidak bisa membantu dengan C ++.
Semoga berhasil!
sumber
Saya melihat dua kandidat masalah dalam fungsi bottleneck Anda:
Pertama adalah bagian "berisi" - ini mungkin merupakan alasan utama kemacetan. Itu iterasi melalui tubuh yang sudah ditemukan untuk setiap tubuh. Mungkin Anda sebaiknya menggunakan beberapa jenis hash_table / hash_map daripada vektor. Maka memasukkan harus lebih cepat (dengan mencari duplikasi). Tetapi saya tidak tahu angka spesifik - saya tidak tahu berapa banyak mayat yang diulang di sini.
Masalah kedua bisa berupa vektor :: clear dan push_back. Jelas mungkin atau mungkin tidak membangkitkan realokasi. Tetapi Anda mungkin ingin menghindarinya. Solusi bisa berupa beberapa flags array. Tetapi Anda mungkin memiliki banyak objek, jadi ingatannya tidak efektif untuk memiliki daftar semua objek untuk setiap objek. Beberapa pendekatan lain mungkin baik, tetapi saya tidak tahu pendekatan apa: /
sumber
Catatan: Saya tidak tahu apa-apa tentang C ++, hanya Java, tetapi Anda harus bisa mengetahui kodenya. Fisika adalah bahasa universal bukan? Saya juga menyadari ini adalah postingan yang berumur setahun, namun saya hanya ingin membagikan ini dengan semua orang.
Saya memiliki pola pengamat yang pada dasarnya, setelah entitas bergerak, ia mengembalikan objek yang telah bertabrakan, termasuk objek NULL. Sederhananya:
( Saya membuat ulang minecraft )
Jadi katakanlah Anda berkeliaran di dunia Anda. setiap kali Anda memanggil
move(1)
Anda maka panggilancollided()
. jika Anda mendapatkan blok yang Anda inginkan, maka mungkin partikel terbang dan Anda dapat bergerak ke kiri dan ke belakang tetapi tidak maju.Menggunakan ini lebih umum daripada hanya minecraft sebagai contoh:
Sederhananya, memiliki sebuah array untuk menunjukkan koordinat yang, secara harfiah bagaimana Java melakukannya, menggunakan pointer.
Dengan menggunakan metode ini masih membutuhkan sesuatu yang lain dari apriori metode deteksi tabrakan. Anda bisa mengulang ini, tapi itu mengalahkan tujuannya. Anda dapat menerapkan ini pada teknik tabrakan Luas, menengah, dan sempit, tetapi sendirian, ini adalah binatang buas terutama saat ia bekerja untuk game 3D dan 2D dengan cukup baik.
Sekarang dengan melihat sekali lagi, ini berarti bahwa, menurut metode minecraft collide () saya, saya akan berakhir di dalam blok, jadi saya harus memindahkan pemain di luarnya. Alih-alih memeriksa pemain, saya perlu menambahkan kotak pembatas yang memeriksa blok mana yang mengenai setiap sisi kotak. Masalah diperbaiki.
paragraf di atas mungkin tidak mudah dengan poligon jika Anda menginginkan keakuratan. Untuk keakuratan, saya akan menyarankan mendefinisikan kotak pembatas poligon yang bukan kotak, tetapi tidak tesselated. jika tidak, maka persegi panjang baik-baik saja.
sumber