Bagaimana cara mendeteksi arah tumbukan objek persegi panjang 2D?

11

Setelah pertanyaan ini , saya perlu bantuan lagi.

Bagaimana saya bisa mengetahui dari sisi persegi panjang mana tabrakan berasal dan bereaksi sesuai?

persegi panjang bertabrakan dengan dari semua sisi

Panah biru adalah jalur yang akan diikuti oleh beberapa objek melingkar jika sebelum dan sesudah bertabrakan dengan kotak.

Bagaimana saya bisa menghitung ini?

NemoStein
sumber

Jawaban:

8

Karena ini didasarkan pada pertanyaan Anda yang lain, saya akan memberikan solusi ketika persegi panjang disejajarkan dengan sumbu.

Pertama, Anda membangun persegi panjang objek Anda saat ini dengan nilai-nilai berikut:

int boxLeft = box.X;
int boxRight = boxLeft + box.Width;
int boxTop = box.Y;
int boxBottom = boxTop + box.Height;

Selanjutnya, Anda harus memiliki posisi objek lama (yang dapat Anda simpan di setiap objek atau cukup beralih ke fungsi) untuk membuat persegi panjang objek lama (ketika tidak bertabrakan):

int oldBoxLeft = box.OldX;
int oldBoxRight = oldBoxLeft + box.Width;
int oldBoxTop = box.OldY;
int oldBoxBottom = oldBoxTop + box.Height;

Sekarang, untuk mengetahui dari mana tabrakan itu berasal, Anda harus menemukan sisi di mana posisi lama tidak berada di area tabrakan dan di mana posisi barunya. Karena, ketika Anda memikirkannya, inilah yang terjadi ketika Anda bertabrakan: sisi yang tidak bertabrakan memasuki persegi panjang lainnya.

Inilah cara Anda dapat melakukannya (fungsi-fungsi ini menganggap bahwa ada tabrakan. Mereka tidak boleh dipanggil jika tidak ada tabrakan):

 bool collidedFromLeft(Object otherObj)
{
    return oldBoxRight < otherObj.Left && // was not colliding
           boxRight >= otherObj.Left;
}

Bangkit dan ulangi.

bool collidedFromRight(Object otherObj)
{
    return oldBoxLeft >= otherObj.Right && // was not colliding
           boxLeft < otherObj.Right;
}

bool collidedFromTop(Object otherObj)
{
    return oldBoxBottom < otherObj.Top && // was not colliding
           boxBottom >= otherObj.Top;
}

bool collidedFromBottom(Object otherObj)
{
    return oldBoxTop >= otherObj.Bottom && // was not colliding
           boxTop < otherObj.Bottom;
}

Sekarang, untuk penggunaan aktual dengan respons tabrakan dari pertanyaan lain:

if (collidedFromTop(otherObj) || collidedFromBottom(otherObj))
    obj.Velocity.Y = -obj.Velocity.Y;
if (collidedFromLeft(otherObj) || collidedFromRight(otherObj))
    obj.Velocity.X = -obj.Velocity.X;

Sekali lagi, ini mungkin bukan solusi terbaik tapi itulah cara saya biasanya pergi untuk deteksi tabrakan.

Jesse Emond
sumber
Sekali lagi, Anda benar! ; D Terima kasih ... (kirimi saya lebih banyak kartu pos dari papan Anda lain kali ... ^ ___ ^)
NemoStein
Ahhh sayangnya saya tidak tahu untuk apa saya bisa menggunakannya .. mungkin lain kali!
Jesse Emond
7

Karena pertanyaan ini sebagian identik dengan pertanyaan ini , saya akan menggunakan kembali beberapa bagian dari jawaban saya untuk mencoba menjawab pertanyaan Anda.


Mari kita mendefinisikan konteks dan beberapa variabel untuk membuat penjelasan berikut lebih dimengerti. Formulir representasi yang akan kami gunakan di sini mungkin tidak sesuai dengan bentuk data Anda sendiri tetapi harus lebih mudah untuk memahami cara ini (memang Anda dapat menggunakan metode berikut menggunakan jenis representasi lain setelah Anda memahami prinsipnya)

Jadi, kami akan mempertimbangkan Axis Aligned Bounding Box (atau Oriented Bounded Box ) dan Entitas yang bergerak .

  • Bounding Box terdiri dari 4 sisi, dan kita akan mendefinisikan masing-masing sebagai:
    Side1 = [x1, y1, x2, y2] (dua poin [x1, y1] dan [x2, y2])

  • Entitas bergerak didefinisikan sebagai vektor kecepatan (posisi + kecepatan):
    posisi [posX, posY] dan kecepatan [speedX, speedY] .


Anda dapat menentukan sisi AABB / OBB mana yang terkena vektor menggunakan metode berikut:

  • 1 / Temukan titik persimpangan antara garis infinite yang melewati empat sisi AABB dan garis infinite melewati posisi entitas (pra-tabrakan) yang menggunakan vektor kecepatan entitas sebagai slop. (Anda dapat menemukan titik tabrakan atau nomor yang tidak ditentukan, yang sesuai dengan paralel atau garis yang tumpang tindih)

  • 2 / Setelah Anda mengetahui titik persimpangan (jika ada), Anda dapat mencari titik yang menjadi batas segmen.

  • 3 / Akhirnya, jika masih ada beberapa titik dalam daftar (vektor kecepatan dapat melewati beberapa sisi), Anda dapat mencari titik terdekat dari asal entitas menggunakan besaran vektor dari persimpangan ke asal entitas.

Kemudian Anda dapat menentukan sudut tabrakan menggunakan produk titik sederhana.

  • 4 / Temukan sudut antara tabrakan dengan menggunakan produk titik dari entitas (mungkin bola?) Dengan vektor sisi hit.

----------

Keterangan lebih lanjut:

  • 1 / Temukan persimpangan

    • a / Tentukan garis infinite (Ax + Bx = D) menggunakan bentuk parametriknya (P (t) = Po + tD).

      Titik asal: Po = [posX, posY]
      Vektor arah: D = [speedX, speedY]

      A = Dy = speedY
      B = -Dx = -speedX
      D = (Po.x * Dy) - (Po.y * Dx) = (posX speedY) - (posY speedX)

      Ax + By = D <====> (speedY x) + (-speedX y) = (posX speedY) - (posY speedX)

      Saya menggunakan nilai-nilai titik entitas untuk menggambarkan metode ini, tetapi ini adalah metode yang persis sama untuk menentukan 4 sisi garis tak terbatas dari kotak pembatas (Gunakan Po = [x1, y1] dan D = [x2-x1; y2-y1] sebagai gantinya).

    • b / Selanjutnya, untuk menemukan persimpangan dua garis tak terbatas kita dapat menyelesaikan sistem berikut:

      A1x + B1x = D1 <== Garis melewati titik entitas dengan vektor kecepatan sebagai slop.
      A2x + B2x = D2 <== Salah satu garis yang melewati sisi AABB.

      yang menghasilkan koordinat berikut untuk intersepsi:

      Intersepsi x = (( B 2 * D 1) - ( B 1 * D 2)) / (( A 1 * B 2) - ( A 2 * B 1))
      Intersepsi y = (( A 1 * D 2) - ( A 2 * D 1)) / (( A 1 * B 2) - ( A 2 * B 1))

      Jika penyebutnya ((A1 * B2) - (A2 * B1)) sama dengan nol, maka kedua garisnya paralel atau tumpang tindih, jika tidak Anda harus menemukan persimpangan.

  • 2 / Tes untuk batas segmen. Karena ini mudah untuk diverifikasi, tidak ada kebutuhan untuk detail lebih lanjut.

  • 3 / Cari titik terdekat. Jika masih ada beberapa titik dalam daftar, kita dapat menemukan sisi mana yang paling dekat dengan titik asal entitas.

    • a / Tentukan vektor dari titik persimpangan ke titik asal entitas

      V = Po - Int = [Po.x - Int.x; Po.y - Int.y]

    • b / Hitung besarnya vektor

      || V || = sqrt (V.x² + V.y²)

    • c / temukan yang terkecil.
  • 4 / Sekarang Anda tahu sisi mana yang akan mengenai, Anda dapat menentukan sudut menggunakan produk titik.

    • a / Let S = [x2-x1; y2-y1] menjadi vektor sisi yang akan dipukul dan E = [speedX; speedY] menjadi vektor kecepatan entitas.

      Menggunakan aturan vektor titik produk kita tahu itu

      S · E = Sx Ex + Sy Ey
      dan
      S · E = || S || || E || cos θ

      Jadi kita dapat menentukan θ dengan memanipulasi sedikit persamaan ini ...

      cos θ = (S · E) / (|| S || || E ||)

      θ = acos ((S · E) / (|| S || || E ||))

      dengan

      S · E = Sx * Ex + Sy * Ey
      || S || = sqrt (Sx² + Sy²)
      || E || = sqrt (Ex² + Ey²)


Catatan: Seperti yang saya katakan di utas pertanyaan lainnya, ini mungkin bukan cara yang paling efisien atau paling sederhana untuk melakukannya, ini hanya yang ada dalam pikiran, dan beberapa bagian dari matematika mungkin bisa membantu.

Saya tidak memverifikasi dengan contoh OBB yang konkret (saya lakukan dengan AABB) tetapi harus juga berfungsi.

Valkea
sumber
6

Cara sederhana adalah dengan menyelesaikan tabrakan, kemudian menerjemahkan kotak tabrakan objek bergerak dengan satu piksel di setiap arah secara bergantian dan melihat mana yang menghasilkan tabrakan.

Jika Anda ingin melakukannya "dengan benar" dan dengan bentuk tabrakan yang diputar atau poligon yang sewenang-wenang, saya sarankan membaca pada Teorema Sumbu Pemisah. Perangkat lunak Metanet (orang-orang yang membuat game N) misalnya memiliki artikel dev yang luar biasa tentang SAT . Mereka juga membahas fisika yang terlibat.

Anko
sumber
2

Salah satu caranya adalah memutar dunia di sekitar persegi panjang Anda. "Dunia" dalam hal ini hanya objek yang Anda pedulikan: persegi panjang, dan bola. Anda memutar persegi panjang di sekitar pusatnya sampai batasnya sejajar dengan sumbu x- / y, kemudian Anda memutar bola dengan jumlah yang sama.

Poin penting di sini adalah Anda memutar bola di sekitar pusat persegi panjang, bukan bola itu sendiri.

Kemudian Anda dapat dengan mudah menguji tabrakan seperti yang Anda lakukan dengan persegi panjang non-rotated lainnya.


Pilihan lain adalah memperlakukan persegi panjang sebagai empat segmen garis yang berbeda, dan menguji tabrakan dengan masing-masing segmen secara terpisah. Ini memungkinkan Anda untuk menguji tabrakan dan mencari tahu sisi mana yang bertabrakan pada saat yang sama.

BlueRaja - Danny Pflughoeft
sumber
1

Saya menggunakan sudut tetap dalam perhitungan saya, tetapi ini akan membantu Anda

void Bullet::Ricochet(C_Rect *r)
{
    C_Line Line;
    //the next two lines are because I detected 
    // a collision in my main loop so I need to take a step back.

    x = x + ceil(speed * ((double)fcos(itofix(angle)) / 65536));
    y = y + ceil(speed * ((double)fsin(itofix(angle)) / 65536));
    C_Point Prev(x,y);

    //the following checks our position to all the lines will give us
    // an answer which line we will hit due to no lines
    // with angles > 90 lines of a rect always shield the other lines.

    Line = r->Get_Closest_Line(Prev);    
    int langle = 0;
    if(!Line.Is_Horizontal())   //we need to rotate the line to a horizontal position
    {
        langle = Line.Get_Point1().Find_Fixed_Angle(Line.Get_Point2());
        angle = angle - langle;  //to give us the new angle of approach
    }
    //at this point the line is horizontal and the bullet is ready to be fixed.
    angle = 256 - angle;
    angle += langle;
}
David Sopala
sumber