Jak wykryć kierunek zderzeń prostokątnych obiektów 2D?

11

Po tym pytaniu potrzebuję dodatkowej pomocy.

Jak mogę dowiedzieć się, z której strony prostokąta pochodzi kolizja i odpowiednio zareagować?

prostokąty zderzają się ze wszystkich stron

Niebieskie strzałki to ścieżki, którymi podążałyby niektóre okrągłe obiekty, gdyby przed i po zderzeniu z pudełkiem.

Jak mogę to obliczyć?

NemoStein
źródło

Odpowiedzi:

8

Ponieważ jest to oparte na twoim drugim pytaniu, dam rozwiązanie, kiedy prostokąt jest wyrównany względem osi.

Najpierw budujesz prostokąt bieżącego obiektu za pomocą następujących wartości:

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

Następnie musisz mieć pozycję starego obiektu (którą możesz zapisać na każdym obiekcie lub po prostu przekazać do funkcji), aby utworzyć prostokąt starego obiektu (gdy nie kolidował):

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

Teraz, aby wiedzieć, skąd pochodzi kolizja, musisz znaleźć stronę, w której poprzednia pozycja nie znajdowała się w obszarze kolizji i gdzie jest nowa pozycja. Ponieważ, gdy o tym pomyślisz, dzieje się tak, gdy się zderzasz: strona, która nie zderzyła się, wchodzi w inny prostokąt.

Oto, jak możesz to zrobić (te funkcje zakładają, że istnieje kolizja. Nie należy ich wywoływać, jeśli nie ma kolizji):

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

Rince i powtórz.

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

Teraz, do faktycznego wykorzystania z odpowiedzią na kolizję z drugiego pytania:

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

Ponownie, to może nie być najlepsze rozwiązanie, ale zwykle tak wybieram wykrywanie kolizji.

Jesse Emond
źródło
Po raz kolejny miałeś rację! ; D Dzięki ... (wyślij mi więcej pocztówek ze swojej planszy następnym razem ... ^ ___ ^)
NemoStein
Ahhh niestety nie wiedziałem, do czego mógłbym go użyć ... może następnym razem!
Jesse Emond
7

Ponieważ pytanie jest częściowo identyczne z tym pytaniem , użyję ponownie niektórych części mojej odpowiedzi, aby spróbować odpowiedzieć na twoje pytanie.


Zdefiniujmy kontekst i niektóre zmienne, aby poniższe wyjaśnienie było bardziej zrozumiałe. Formularz reprezentacji, którego będziemy tutaj używać, prawdopodobnie nie pasuje do twoich danych, ale powinno być prostsze do zrozumienia w ten sposób (w rzeczywistości możesz skorzystać z następujących metod, używając innych rodzajów prezentacji, gdy zrozumiesz zasadę)

Rozważymy więc Wyrównaną Osie Obwiednię (lub Orientowaną Obramowanie ) i poruszającą się Istotę .

  • Obwiednia składa się z 4 boków, a my zdefiniujemy każdą z nich jako:
    Side1 = [x1, y1, x2, y2] (dwa punkty [x1, y1] i [x2, y2])

  • Poruszająca się jednostka jest zdefiniowana jako wektor prędkości (pozycja + prędkość):
    pozycja [posX, posY] i prędkość [speedX, speedY] .


Możesz ustalić, która strona AABB / OBB jest uderzona przez wektor, używając następującej metody:

  • 1 / Znajdź punkty przecięcia między nieskończonymi liniami przechodzącymi przez cztery strony AABB i nieskończoną linią przechodzącą przez pozycję bytu (przed kolizją), które wykorzystują wektor prędkości bytu jako nachylenie. (Możesz znaleźć punkt kolizji lub niezdefiniowany numer, który odpowiada równoległym lub nakładającym się liniom)

  • 2 / Po poznaniu punktów przecięcia (jeśli istnieją) możesz wyszukać te, które znajdują się w granicach segmentu.

  • 3 / Na koniec, jeśli na liście wciąż znajduje się wiele punktów (wektor prędkości może przechodzić przez wiele stron), możesz wyszukać najbliższy punkt od początku bytu, używając wielkości wektora od przecięcia do początku bytu.

Następnie możesz określić kąt zderzenia za pomocą prostego iloczynu punktowego.

  • 4 / Znajdź kąt między kolizją za pomocą iloczynu iloczynu wektora bytu (prawdopodobnie piłki?) Z wektorem strony uderzenia.

----------

Więcej szczegółów:

  • 1 / Znajdź skrzyżowania

    • a / Określ linie nieskończone (Ax + Bx = D), używając ich postaci parametrycznych (P (t) = Po + tD).

      Punkt początkowy: Po = [posX, posY]
      Wektor kierunku: D = [prędkość X, prędkość Y]

      A = Dy = prędkość Y
      B = -Dx = prędkość X
      D = (Po.x * Dy) - (Po.y * Dx) = (posX speedY) - (posY speedX)

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

      Użyłem wartości punktów bytu do zilustrowania metody, ale jest to dokładnie ta sama metoda, aby określić 4 boczne nieskończone linie obwiedni (użyj Po = [x1, y1] i D = [x2-x1; y2-y1] zamiast).

    • b / Następnie, aby znaleźć przecięcie dwóch nieskończonych linii, możemy rozwiązać następujący układ:

      A1x + B1x = D1 <== Linia przechodząca przez punkt bytu z wektorem prędkości jako nachylenie.
      A2x + B2x = D2 <== Jedna z linii przechodzących przez boki AABB.

      co daje następujące współrzędne dla przechwytywania:

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

      Jeśli mianownik ((A1 * B2) - (A2 * B1)) jest równy zero, wówczas obie linie są równoległe lub zachodzą na siebie, w przeciwnym razie powinieneś znaleźć skrzyżowanie.

  • 2 / Test granic segmentu. Ponieważ jest to łatwe do zweryfikowania, nie ma potrzeby podawania dodatkowych szczegółów.

  • 3 / Wyszukaj najbliższy punkt. Jeśli na liście nadal znajduje się wiele punktów, możemy dowiedzieć się, która strona jest najbliżej punktu początkowego encji.

    • a / Określ wektor przechodzący od punktu przecięcia do punktu początkowego elementu

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

    • b / Oblicz wielkość wektora

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

    • c / znajdź najmniejszy.
  • 4 / Teraz, kiedy już wiesz, która strona zostanie trafiona, możesz określić kąt za pomocą iloczynu punktowego.

    • a / Niech S = [x2-x1; y2-y1] będzie wektorem bocznym, który zostanie trafiony, a E = [speedX; speedY] być wektorem prędkości bytu.

      Używając reguły iloczynu wektorowego wiemy o tym

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

      Możemy więc ustalić θ manipulując trochę tym równaniem ...

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

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

      z

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


Uwaga: Jak powiedziałem w innym wątku pytania, prawdopodobnie nie jest to najskuteczniejszy ani najprostszy sposób na zrobienie tego, to właśnie przychodzi mi na myśl, a pewna część matematyki może pomóc.

Nie zweryfikowałem tego konkretnym przykładem OBB (zrobiłem to z AABB), ale to też powinno działać.

Valkea
źródło
6

Prostym sposobem jest rozwiązanie kolizji, a następnie przełożenie pola kolizji ruchomego obiektu o jeden piksel w każdym kierunku z kolei i sprawdzenie, które z nich powodują kolizję.

Jeśli chcesz to zrobić „poprawnie” i przy obróconych kształtach kolizji lub dowolnych wielokątach, proponuję przeczytać o Twierdzeniu o Osiach Oddzielających. Na przykład oprogramowanie Metanet (ludzie, którzy stworzyli grę N) ma niesamowity artykuł dla programistów na temat SAT . Omawiają także związaną z tym fizykę.

Anko
źródło
2

Jednym ze sposobów byłoby obrócenie świata wokół prostokąta. „Świat” w tym przypadku to tylko przedmioty, na których Ci zależy: prostokąt i piłka. Obracasz prostokąt wokół jego środka, dopóki jego granice nie zostaną wyrównane z osiami x- / y, a następnie obrócisz piłkę o tę samą wartość.

Ważną kwestią jest to, że obracasz piłkę wokół środka prostokąta, a nie własnego.

Następnie możesz łatwo przetestować kolizję, tak jak w przypadku każdego innego nieobróconego prostokąta.


Inną opcją jest traktowanie prostokąta jako czterech odrębnych segmentów linii i testowanie kolizji z każdym z nich osobno. Pozwala to przetestować kolizję i dowiedzieć się, która strona zderzyła się w tym samym czasie.

BlueRaja - Danny Pflughoeft
źródło
1

Użyłem stałych kątów w moich obliczeniach, ale to powinno ci trochę pomóc

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
źródło