Wie erkenne ich die Richtung von rechteckigen 2D-Objektkollisionen?

11

Nach dieser Frage brauche ich noch etwas Hilfe.

Wie kann ich herausfinden, von welcher Seite eines Rechtecks ​​eine Kollision stammt, und entsprechend reagieren?

Rechtecke werden von allen Seiten kollidiert

Die blauen Pfeile sind die Pfade, denen einige kreisförmige Objekte vor und nach der Kollision mit der Box folgen würden.

Wie kann ich das berechnen?

NemoStein
quelle

Antworten:

8

Da dies auf Ihrer anderen Frage basiert, gebe ich eine Lösung für den Fall, dass das Rechteck achsenausgerichtet ist.

Zunächst bauen Sie das Rechteck Ihres aktuellen Objekts mit den folgenden Werten auf:

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

Als nächstes müssen Sie die Position des alten Objekts haben (die Sie auf jedem Objekt speichern oder einfach an eine Funktion übergeben können), um das Rechteck des alten Objekts zu erstellen (wenn es nicht kollidierte):

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

Um zu wissen, woher die Kollision stammt, müssen Sie die Seite finden, auf der sich die alte Position nicht im Kollisionsbereich befand und auf der sich ihre neue Position befindet. Denn wenn Sie daran denken, passiert Folgendes, wenn Sie kollidieren: Eine Seite, die nicht kollidiert, tritt in ein anderes Rechteck ein.

So können Sie dies tun (diese Funktionen setzen eine Kollision voraus. Sie sollten nicht aufgerufen werden, wenn keine Kollision vorliegt):

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

Rince und wiederholen.

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

Nun zur tatsächlichen Verwendung mit der Kollisionsantwort aus der anderen Frage:

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

Auch dies ist möglicherweise nicht die beste Lösung, aber so gehe ich normalerweise zur Kollisionserkennung vor.

Jesse Emond
quelle
Sie hatten wieder einmal Recht! ; D Danke ... (schick mir beim nächsten Mal mehr Postkarten von deinem Board ... ^ ___ ^)
NemoStein
Ahhh, leider wusste ich nicht, wofür ich es hätte verwenden können. Vielleicht beim nächsten Mal!
Jesse Emond
7

Da die Frage teilweise mit dieser Frage identisch ist , werde ich einige Teile meiner Antwort erneut verwenden, um zu versuchen, Ihre Frage zu beantworten.


Definieren wir einen Kontext und einige Variablen, um die folgende Erklärung verständlicher zu machen. Das Darstellungsformular, das wir hier verwenden, passt wahrscheinlich nicht zur Form Ihrer eigenen Daten, sollte jedoch einfacher zu verstehen sein (tatsächlich können Sie die folgenden Methoden verwenden, indem Sie andere Arten von Darstellungen verwenden, sobald Sie das Prinzip verstanden haben).

Wir werden also eine achsenausgerichtete Begrenzungsbox (oder eine orientierte Begrenzungsbox ) und eine sich bewegende Entität betrachten .

  • Die Begrenzungsbox besteht aus 4 Seiten, und wir definieren jede als:
    Seite1 = [x1, y1, x2, y2] (zwei Punkte [x1, y1] und [x2, y2])

  • Die sich bewegende Entität ist definiert als ein Geschwindigkeitsvektor (Position + Geschwindigkeit):
    eine Position [posX, posY] und eine Geschwindigkeit [speedX, speedY] .


Mit der folgenden Methode können Sie bestimmen, welche Seite eines AABB / OBB von einem Vektor getroffen wird:

  • 1 / Finden Sie die Schnittpunkte zwischen den unendlichen Linien, die durch die vier Seiten des AABB verlaufen, und der unendlichen Linie, die durch die Entitätsposition verläuft (Vorkollision), wobei der Entitätsgeschwindigkeitsvektor als Steigung verwendet wird. (Sie können entweder einen Kollisionspunkt oder eine undefinierte Zahl finden, die Parallelen oder überlappenden Linien entspricht.)

  • 2 / Sobald Sie die Schnittpunkte kennen (falls vorhanden), können Sie nach solchen suchen, die innerhalb der Segmentgrenzen liegen.

  • 3 / Wenn die Liste noch mehrere Punkte enthält (der Geschwindigkeitsvektor kann mehrere Seiten durchlaufen), können Sie anhand der Größen des Vektors vom Schnittpunkt zum Entitätsursprung nach dem nächstgelegenen Punkt vom Entitätsursprung suchen.

Anschließend können Sie den Kollisionswinkel mit einem einfachen Punktprodukt bestimmen.

  • 4 / Ermitteln Sie den Kollisionswinkel zwischen einem Punktprodukt des Entitätsvektors (wahrscheinlich einer Kugel?) Und dem Treffer-Seitenvektor.

----------

Mehr Details:

  • 1 / Kreuzungen suchen

    • a / Bestimmen Sie die unendlichen Linien (Ax + Bx = D) anhand ihrer Parameterformen (P (t) = Po + tD).

      Punktursprung: Po = [posX, posY]
      Richtungsvektor: 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)

      Ich habe die Entitätspunktwerte verwendet, um die Methode zu veranschaulichen, aber dies ist genau dieselbe Methode, um die 4 seitlichen unendlichen Linien des Begrenzungsrahmens zu bestimmen (Verwenden Sie Po = [x1, y1] und D = [x2-x1; y2-y1] stattdessen).

    • b / Um den Schnittpunkt zweier unendlicher Linien zu finden, können wir das folgende System lösen:

      A1x + B1x = D1 <== Linie, die durch den Entitätspunkt mit dem Geschwindigkeitsvektor als Steigung verläuft.
      A2x + B2x = D2 <== Eine der Linien, die durch die AABB-Seiten verlaufen.

      Dies ergibt die folgenden Koordinaten für das Abfangen:

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

      Wenn der Nenner ((A1 * B2) - (A2 * B1)) gleich Null ist, sind beide Linien parallel oder überlappen sich, andernfalls sollten Sie einen Schnittpunkt finden.

  • 2 / Test auf die Segmentgrenzen. Da dies einfach zu überprüfen ist, sind keine weiteren Details erforderlich.

  • 3 / Suchen Sie nach dem nächstgelegenen Punkt. Wenn die Liste noch mehrere Punkte enthält, können wir herausfinden, welche Seite dem Entitätsursprungspunkt am nächsten liegt.

    • a / Bestimmen Sie den Vektor, der vom Schnittpunkt zum Entitätsursprungspunkt verläuft

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

    • b / Berechnen Sie die Vektorgröße

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

    • c / finde den kleinsten.
  • 4 / Nachdem Sie nun wissen, welche Seite getroffen wird, können Sie den Winkel mit einem Punktprodukt bestimmen.

    • a / Sei S = [x2-x1; y2-y1] ist der Seitenvektor, der getroffen wird, und E = [speedX; speedY] sei der Geschwindigkeitsvektor der Entität.

      Mit der Vektorpunktproduktregel wissen wir das

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

      Wir können also θ bestimmen, indem wir diese Gleichung ein wenig manipulieren ...

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

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

      mit

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


Hinweis: Wie ich im anderen Fragenthread sagte, ist dies wahrscheinlich weder der effizienteste noch der einfachste Weg, dies zu tun. Dies ist genau das, was mir in den Sinn kommt, und ein Teil der Mathematik könnte vielleicht helfen.

Ich habe nicht mit einem konkreten OBB-Beispiel verifiziert (ich habe es mit einem AABB gemacht), aber es sollte auch funktionieren.

Walküre
quelle
6

Eine einfache Möglichkeit besteht darin, die Kollision aufzulösen und dann die Kollisionsbox des sich bewegenden Objekts nacheinander um ein Pixel in jede Richtung zu verschieben und zu sehen, welche zu einer Kollision führen.

Wenn Sie es "richtig" und mit gedrehten Kollisionsformen oder beliebigen Polygonen machen möchten, empfehle ich, den Satz der Trennachse nachzulesen. Die Metanet-Software (die Leute, die das N-Spiel entwickelt haben) hat zum Beispiel einen großartigen Entwicklerartikel über SAT . Sie diskutieren auch die Physik.

Anko
quelle
2

Eine Möglichkeit wäre, die Welt um Ihr Rechteck zu drehen. "Die Welt" sind in diesem Fall nur die Objekte, die Sie interessieren: das Rechteck und der Ball. Sie drehen das Rechteck um seine Mitte, bis seine Grenzen mit den x- / y-Achsen ausgerichtet sind, und drehen dann den Ball um den gleichen Betrag.

Der wichtige Punkt hierbei ist, dass Sie den Ball um die Mitte des Rechtecks ​​drehen, nicht um seine eigene.

Dann können Sie einfach wie bei jedem anderen nicht gedrehten Rechteck auf Kollision testen.


Eine andere Möglichkeit besteht darin, das Rechteck als vier verschiedene Liniensegmente zu behandeln und die Kollision mit jedem von ihnen separat zu testen. Auf diese Weise können Sie auf Kollision testen und herausfinden, welche Seite gleichzeitig kollidiert wurde.

BlueRaja - Danny Pflughoeft
quelle
1

Ich habe bei meinen Berechnungen feste Winkel verwendet, aber dies sollte Ihnen helfen

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
quelle