Sebagian demi optimasi, sebagian untuk tujuan pembelajaran, saya akan berani bertanya: Bagaimana saya bisa paling efisien memeriksa apakah titik 2D P
ada di dalam persegi panjang yang diputar 2D XYZW
, menggunakan C # atau C ++?
Saat ini, apa yang saya lakukan adalah menggunakan algoritma "point in triangle" yang ditemukan dalam buku Real Time Collision Detection , dan menjalankannya dua kali (untuk dua segitiga yang membentuk persegi panjang, katakanlah XYZ dan XZW):
bool PointInTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
{
// Compute vectors
Vector2 v0 = C - A;
Vector2 v1 = B - A;
Vector2 v2 = P - A;
// Compute dot products
float dot00 = Vector2.Dot(v0, v0);
float dot01 = Vector2.Dot(v0, v1);
float dot02 = Vector2.Dot(v0, v2);
float dot11 = Vector2.Dot(v1, v1);
float dot12 = Vector2.Dot(v1, v2);
// Compute barycentric coordinates
float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
// Check if point is in triangle
if(u >= 0 && v >= 0 && (u + v) < 1)
{ return true; } else { return false; }
}
bool PointInRectangle(Vector2 X, Vector2 Y, Vector2 Z, Vector2 W, Vector2 P)
{
if(PointInTriangle(X,Y,Z,P)) return true;
if(PointInTriangle(X,Z,W,P)) return true;
return false;
}
Namun, saya merasa mungkin ada cara yang lebih bersih dan lebih cepat. Secara khusus, untuk mengurangi jumlah operasi matematika.
c#
collision-detection
geometry
optimization
Louis15
sumber
sumber
Jawaban:
Optimalisasi yang mudah dan langsung akan mengubah kondisi akhir di
PointInTriangle
:kode sudah cukup banyak
PointInRectangle
, syaratnya(u + v) < 1
ada di sana untuk memeriksa jika tidak dalam "kedua" segitiga persegi panjang.Sebagai alternatif, Anda juga dapat melakukan
isLeft
(contoh kode pertama pada halaman, juga sangat menjelaskan) tes empat kali, dan memeriksa bahwa mereka semua mengembalikan hasil dengan tanda yang sama (yang tergantung pada apakah poin diberikan dalam searah jarum jam atau berlawanan arah jarum jam) untuk intinya berada di dalam. Ini juga berfungsi untuk poligon cembung lainnya.sumber
isLeft
metode ini. Tidak memerlukan fungsi trigonometri (sepertiVector2.Dot
halnya), yang mempercepat banyak hal.public static bool PointInRectangle(Vector2 P, Vector2 X, Vector2 Y, Vector2 Z, Vector2 W) { return !(( (Y.x - X.x) * (P.y - X.y) - (P.x - X.x) * (Y.y - X.y) ) < 0 || ( (Z.x - Y.x) * (P.y - Y.y) - (P.x - Y.x) * (Z.y - Y.y) ) < 0 || ( (W.x - Z.x) * (P.y - Z.y) - (P.x - Z.x) * (W.y - Z.y) ) < 0 || ( (X.x - W.x) * (P.y - W.y) - (P.x - W.x) * (X.y - W.y) ) < 0 ); }
isLeft
sebagai inline kompiler akan melakukan sesuatu yang serupa untuk Anda (dan mungkin lebih baik dari yang Anda bisa, karena para insinyur yang menulis kompiler mengetahui CPU terbaik, memilih opsi apa pun yang tercepat) membuat kode Anda lebih mudah dibaca dengan efek yang sama atau lebih baik.Sunting: Komentar OP telah skeptis tentang efisiensi cek terikat lingkaran negatif yang disarankan untuk meningkatkan algoritma agar dapat memeriksa apakah titik 2D sewenang-wenang terletak di dalam persegi panjang yang diputar dan / atau bergerak. Mengotak-atik mesin game 2D saya (OpenGL / C ++), saya melengkapi jawaban saya dengan memberikan tolok ukur kinerja algoritma saya terhadap algoritma point-in-rectangle-check OP saat ini (dan variasi).
Saya awalnya menyarankan untuk meninggalkan algoritma di tempat (karena hampir optimal), tetapi menyederhanakan melalui logika permainan belaka: (1) menggunakan lingkaran pra-diproses di sekitar persegi panjang asli; (2) melakukan pengecekan jarak dan jika titik terletak di dalam lingkaran yang diberikan; (3) gunakan OP atau algoritma langsung lainnya (saya sarankan algoritma isLeft seperti yang disediakan dalam jawaban lain). Logika di belakang saran saya adalah bahwa memeriksa apakah suatu titik di dalam lingkaran jauh lebih efisien daripada pemeriksaan batas dari persegi panjang yang diputar atau poligon lain.
Skenario awal saya untuk uji benchmark adalah menjalankan sejumlah besar titik yang muncul dan menghilang (yang posisinya berubah di setiap loop permainan) di ruang terbatas yang akan diisi dengan sekitar 20 kotak yang berputar / bergerak. Saya telah menerbitkan video ( tautan youtube ) untuk tujuan ilustrasi. Perhatikan parameternya: jumlah titik, angka, atau persegi yang muncul secara acak. Saya akan melakukan benchmark dengan parameter berikut:
OFF : Algoritma langsung seperti yang disediakan oleh OP tanpa pemeriksaan negatif batas lingkaran
ON : Menggunakan lingkaran per-diproses (batas) di sekitar persegi panjang sebagai pemeriksaan pengecualian pertama
ON + Stack : Membuat batas lingkaran saat run-time di dalam loop pada stack
ON + Kuadrat Jarak : Menggunakan jarak kuadrat sebagai optimasi lebih lanjut untuk menghindari pengambilan algoritma kuadrat yang lebih mahal (Pieter Geerkens).
Berikut ini adalah ringkasan dari berbagai kinerja algoritma yang berbeda dengan menunjukkan waktu yang diperlukan untuk beralih melalui loop.
Sumbu x menunjukkan peningkatan kompleksitas dengan menambahkan lebih banyak titik (dan dengan demikian memperlambat loop). (Misalnya, pada 1000 titik yang muncul secara acak memeriksa dalam ruang tertutup dengan 20 persegi panjang, loop berulang dan memanggil algoritme 20000 kali.) Sumbu-y menunjukkan waktu yang diperlukan (ms) untuk menyelesaikan seluruh loop menggunakan resolusi tinggi timer kinerja. Lebih dari 20 ms akan bermasalah untuk game yang layak karena tidak akan mengambil keuntungan dari fps tinggi untuk menginterpolasi animasi yang halus dan permainan mungkin muncul 'kasar' di kali.
Hasil 1 : Algoritma terikat terikat pra-diproses dengan cek negatif cepat dalam loop meningkatkan kinerja sebesar 1900% dibandingkan dengan algoritma biasa (5% dari waktu loop asli tanpa cek). Hasilnya memegang kira-kira sebanding dengan jumlah iterasi dalam satu loop, sehingga tidak masalah jika kita memeriksa 10 atau 10000 poin yang muncul secara acak. Dengan demikian, dalam ilustrasi ini orang dapat meningkatkan jumlah objek dengan aman hingga 10rb tanpa merasakan kehilangan kinerja.
Hasil 2 : Telah dikomentari oleh komentar sebelumnya bahwa algoritma mungkin lebih cepat tetapi intensif memori. Namun, perhatikan bahwa menyimpan pelampung untuk ukuran lingkaran pra-proses hanya membutuhkan 4 byte. Ini seharusnya tidak menimbulkan masalah nyata kecuali OP berencana untuk menjalankan 100000 objek secara bersamaan. Alternatif dan pendekatan memori efisien adalah untuk menghitung ukuran lingkaran maksimum pada tumpukan dalam loop dan membiarkannya keluar dari ruang lingkup dengan setiap iterasi dan dengan demikian praktis tidak menggunakan memori untuk beberapa harga kecepatan yang tidak diketahui. Memang, hasilnya menunjukkan bahwa pendekatan ini memang lebih lambat daripada menggunakan ukuran lingkaran pra-diproses, tetapi masih menunjukkan peningkatan kinerja yang cukup besar sekitar 1150% (yaitu 8% dari waktu pemrosesan asli).
Hasil 3 : Saya lebih meningkatkan algoritma hasil 1 dengan menggunakan jarak kuadrat bukan jarak yang sebenarnya dan dengan demikian mengambil operasi root kuadrat komputasi mahal. Ini hanya sedikit meningkatkan kinerja (2400%). (Catatan: Saya juga mencoba tabel hash untuk array pra-diproses untuk perkiraan akar kuadrat dengan hasil yang serupa tetapi sedikit lebih buruk)
Hasil 4 : Saya selanjutnya memeriksa bergerak / bertabrakan persegi panjang di sekitar; Namun, ini tidak mengubah hasil dasar (seperti yang diharapkan) karena pemeriksaan logis pada dasarnya tetap sama.
Hasil 5 : Saya memvariasikan jumlah persegi panjang dan menemukan bahwa algoritma menjadi lebih efisien semakin sedikit ruang crowdy diisi (tidak ditampilkan dalam demo). Hasilnya juga agak diharapkan, karena probabilitas berkurang untuk titik muncul dalam ruang kecil antara lingkaran dan batas-batas objek. Pada ekstrem yang lain, saya mencoba untuk menambah jumlah persegi panjang juga 100 dalam ruang kecil yang sama DAN memvariasikannya secara dinamis dalam ukuran saat run time di dalam loop (sin (iterator)). Ini masih berkinerja sangat baik dengan peningkatan kinerja sebesar 570% (atau 15% dari waktu loop asli).
Hasil 6 : Saya menguji algoritma alternatif yang disarankan di sini dan menemukan perbedaan yang sangat kecil tetapi tidak signifikan dalam kinerja (2%). Algoritme IsLeft yang menarik dan lebih sederhana memiliki kinerja yang sangat baik dengan peningkatan kinerja sebesar 17% (85% dari waktu perhitungan awal), tetapi tidak ada efisiensi algoritma pengecekan negatif yang cepat.
Maksud saya adalah pertama-tama mempertimbangkan desain ramping dan logika game, terutama ketika berhadapan dengan batas dan peristiwa tabrakan. Algoritma OPs saat ini sudah cukup efisien dan optimasi lebih lanjut tidak sepenting mengoptimalkan konsep yang mendasarinya sendiri. Selain itu, baik untuk mengomunikasikan ruang lingkup dan tujuan permainan, karena efisiensi suatu algoritma sangat tergantung pada mereka.
Saya menyarankan untuk selalu berusaha melakukan tolok ukur algoritme yang kompleks selama tahap desain game karena hanya dengan melihat kode biasa mungkin tidak mengungkapkan kebenaran tentang kinerja run-time aktual. Algoritme yang disarankan mungkin tidak diperlukan di sini, jika, misalnya, seseorang hanya ingin menguji apakah kursor mouse berada di dalam persegi panjang atau tidak, atau, ketika sebagian besar objek sudah menyentuh. Jika sebagian besar titik memeriksa berada dalam persegi panjang, algoritme akan kurang efisien. (Namun, maka akan mungkin untuk menetapkan batas 'lingkaran dalam' sebagai pemeriksaan negatif sekunder.) Pemeriksaan batas lingkaran / bola sangat berguna untuk setiap deteksi tabrakan yang layak dari sejumlah besar objek yang secara alami memiliki ruang di antaranya. .
sumber
Mendefinisikan sebuah persegi panjang dengan 4 poin memungkinkan untuk membuat trapesium. Namun, jika Anda akan mendefinisikannya dengan x, y, lebar, tinggi dan rotasi di tengahnya, Anda bisa memutar titik yang Anda periksa dengan rotasi terbalik persegi panjang Anda (sekitar asal yang sama) dan kemudian periksa apakah itu dalam persegi panjang asli.
sumber
Saya tidak punya waktu untuk membandingkan ini, tetapi saran saya adalah untuk menyimpan matriks transformasi yang mengubah persegi panjang menjadi persegi yang sejajar dalam rentang x dan y dari 0 hingga 1. Dengan kata lain menyimpan matriks yang mengubah satu sudut persegi panjang menjadi (0,0) dan sebaliknya menjadi (1,1).
Ini tentu saja akan lebih mahal jika persegi panjang itu banyak dipindahkan dan tabrakan diperiksa agak jarang, tetapi jika ada lebih banyak pemeriksaan daripada pembaruan ke persegi panjang itu setidaknya akan lebih cepat daripada pendekatan asli pengujian terhadap dua segitiga, karena produk enam titik akan diganti dengan satu perkalian matriks.
Tapi seperti biasa kecepatan algoritma ini sangat tergantung pada jenis pemeriksaan yang Anda harapkan dilakukan. Jika sebagian besar titik bahkan tidak dekat dengan persegi panjang yang melakukan pemeriksaan jarak sederhana (mis. (Point.x - firstCorner.x)> aLargeDistance) dapat menghasilkan percepatan yang besar, sementara itu mungkin bahkan memperlambat segalanya jika hampir semua titik berada di dalam persegi panjang.
EDIT: Beginilah tampilan kelas Rectangle saya:
Ini adalah daftar lengkap tolok ukur saya:
Kode itu tentu saja tidak indah, tetapi saya tidak segera melihat bug utama. Dengan kode itu saya mendapatkan hasil yang menunjukkan bahwa solusi saya sekitar dua kali lebih cepat jika persegi panjang dipindahkan di antara setiap pemeriksaan. Jika tidak bergerak maka kode saya sepertinya lebih dari lima kali lebih cepat.
Jika Anda tahu bagaimana kode akan digunakan, Anda bahkan dapat mempercepatnya sedikit lebih dengan memisahkan transformasi dan pemeriksaan menjadi dua dimensi. Misalnya dalam permainan balap mungkin akan lebih cepat untuk memeriksa koordinat terlebih dahulu yang menunjuk ke arah mengemudi, karena banyak rintangan akan ada di depan atau di belakang mobil, tetapi hampir tidak ada yang akan kanan atau kiri itu.
sumber