Schnelltipp Kollisionserkennung zwischen einem Kreis und einem Liniensegment

In unserem vorherigen Quick Tip haben wir die Kollisionserkennung zwischen einer unendlichen Linie und einem Kreis behandelt. Das Problem bestand jedoch darin, dass die Linie sich weiter als das sichtbare Liniensegment erstreckt. es erstreckt sich tatsächlich in eine Hyperebene. In diesem Quick Tip beschränken wir unsere Kollisionserkennung auf die einer Linie Segment nur.


Endergebnisvorschau

Wir werden auf dieses Ergebnis hinarbeiten:

Klicken Sie auf die Schaltfläche Neustart, um die Kreise oben auf der Bühne neu zu positionieren.


Schritt 1: Zwei Ansätze

Es gibt zahlreiche Ansätze, die Kollisionserkennung auf ein Liniensegment zu beschränken. Wir betrachten dieses Mal zwei Ansätze. Der erste Ansatz ist mathematisch etwas strenger als der zweite, aber dies sind Konzepte, von denen Sie, wenn Sie sie erfolgreich verstehen, in Zukunft sicherlich von Nutzen sein werden. Beide Ansätze manipulieren die Eigenschaft des Punktprodukts, ein Maß dafür zu sein, wie parallel zwei gegebene Vektoren sind.

Schauen wir uns den ersten Ansatz an. Angenommen, A und B sind Vektoren. Wenn A und B parallel sind oder zumindest in dieselbe Richtung zeigen, erzeugt das Punktprodukt zwischen A und B eine positive Zahl. Wenn A und B direkt aufeinander zeigen - oder zumindest in entgegengesetzte Richtungen zeigen -, erzeugt das Punktprodukt zwischen A und B eine negative Zahl. Wenn A und B orthogonal sind (90 ° zueinander bilden), erzeugt das Produkt 0.

Das folgende Diagramm fasst diese Beschreibung zusammen.


Schritt 2: Verknüpfen Sie das Punktprodukt mit den Bedingungen

Wir müssen die Vektoren B und C aus beiden Enden des Liniensegments bilden, damit ihr Punktprodukt mit dem Vektor des Liniensegments A bestimmen kann, ob der Kreis innerhalb des Segments liegt.

Beachten Sie das folgende Diagramm. Wenn sich der Kreis innerhalb des Segments befindet, ist der Wert des Punktprodukts zwischen A und B positiv und der zwischen A und C ist negativ.

Das folgende Diagramm zeigt, wie sich das Punktprodukt ändert, je nachdem, ob sich der Kreis außerhalb oder innerhalb des Liniensegments befindet. Beachten Sie die Unterschiede im Wert des Punktprodukts.

Beachten Sie auch, dass "innerhalb des Liniensegments" nicht bedeutet, dass der Kreis das Liniensegment notwendigerweise schneidet, nur dass es innerhalb der zwei dünnen Linien im obigen Diagramm liegt.

Wenn also eine Kollision zwischen Linie und Kreis auftritt, wie wir im vorherigen Schnelltipp gesehen haben, müssen wir weiter untersuchen, ob sich der Kreis innerhalb des Liniensegments befindet. Wenn ja, dann wissen wir mit Sicherheit, dass es eine echte Kreuzung gibt.


Schritt 3: Implementierung

In Schritt 2 wurde das Konzept erläutert, mit dem die Kollisionserkennung auf das Liniensegment beschränkt wird. Es gibt jedoch immer noch einen Fehler in der Präzision. Sie sehen, der definierte Bereich ist etwas geneigt; Wir sollten uns bemühen, den Bereich zu verwenden, der gemäß der folgenden Abbildung definiert ist.

Dies ist einfach: Wir berechnen einfach D als horizontale Projektion von A. Dann verwenden wir D, um das Produkt mit B und C zu dotieren. Alle in Schritt 2 erläuterten Bedingungen bleiben erhalten, jedoch nicht ein gekipptes Segment haben wir einen vertikalen Bereich definiert.

Diese Korrektur kann visuell erkannt werden, wenn der Kreis groß ist. Wenn der Kreis klein wäre, würde sein Zentrum so nahe an der Linie liegen, dass dieser visuelle Fehler schwer zu erkennen wäre. Wir könnten also mit diesem leicht geneigten Bereich davonkommen und uns etwas Rechenleistung sparen.

Trotzdem werde ich versuchen, die Dinge richtig zu machen. Sie können Ihren Ansatz auswählen, indem Sie die Bedingung geringfügig ändern.


Schritt 4: Implementierung

Das erste ActionScript-Snippet hier setzt den Vektor D (v_line_onX)

 // Att2: Holen des horizontalen Vektors var line_onX: Number = line.projectionOn (new Vector2D (1, 0)); v_line_onX = neuer Vector2D (1, 0); v_line_onX.setMagnitude (line_onX);

Hinweis: Wir verwenden hier Klassen aus meinen vorherigen Tutorials. Vector2D wurde in Gravity in Action eingeführt, aber Sie müssen das nicht lesen, um die Klasse zu verwenden. Sie ist im Quellendownload enthalten.

Das zweite ActionScript-Snippet hier setzt B (c1_circle) und C (c2_circle) und prüft auf die Kollision und ob sich der Kreis im Segment befindet oder nicht.

 private Funktionsaktualisierung (e: Event): void for (var i: int = 0; i < circles.length; i++)  //calculating line's perpendicular distance to ball var c1_circle:Vector2D = new Vector2D(circles[i].x - x1, circles[i].y - y1); var c1_circle_onNormal:Number = c1_circle.projectionOn(leftNormal); //Att2: get vector from c2 to circle var c2_circle:Vector2D = new Vector2D(circles[i].x - x2, circles[i].y - y2); circles[i].y += 2; if ( c1_circle_onNormal <= circles[i].radius && v_line_onX.dotProduct(c1_circle) > 0 && v_line_onX.dotProduct (c2_circle) < 0 ) //if collision happened, undo movement circles[i].y -= 2;   

Schritt 5: Das Ergebnis

Hier ist das Ergebnis für den ersten Ansatz. Klicken Sie auf die Schaltfläche, um die Positionen aller Kreise auf die Bühne zurückzusetzen.


Schritt 6: Zweiter Ansatz

Der zweite Ansatz ist viel einfacher. Ich werde versuchen, diesmal vom Ende aus rückwärts zu arbeiten.

Beachten Sie das folgende Diagramm. Das Liniensegment reicht von c1 bis c2. Es ist klar, dass kollidieren1 und collide3 sind beide außerhalb des Liniensegments und nur das collide2 ist innerhalb des Liniensegments.

Seien v1, v2 und v3 Vektoren von c1 zu den jeweiligen Kreisen. Nur v2 und v3 sind parallel - oder zeigen zumindest in ähnliche Richtungen wie der Linienvektor (c1 bis c2). Durch Prüfen auf einen positiven Wert im Punktprodukt zwischen dem Linienvektor und jedem dieser Vektoren von c1 bis zu den entsprechenden Kreiszentren (v1, v2, v3) können wir leicht feststellen, dass kollid1 außerhalb des Liniensegments liegt. Mit anderen Worten, c1. v1 .

Als nächstes werden wir eine Methode entwickeln, um zu bestimmen, dass collide3 außerhalb des Liniensegments liegt. Das sollte einfach sein. Es ist offensichtlich, dass die Projektion von v3 entlang des Linienvektors die Länge des Liniensegments überschreitet. Wir werden dieses Merkmal verwenden, um collide3 zu entfernen.

Lassen Sie mich den zweiten Ansatz zusammenfassen:

  • Zuerst prüfen wir auf einen Schnittpunkt zwischen der unendlichen Linie und dem Kreis.
  • Wenn es eine Kreuzung gibt, untersuchen Sie Folgendes weiter, um festzustellen, ob sie innerhalb des Liniensegments auftritt:
    • Vergewissern Sie sich, dass ein positiver Wert erzeugt wird, wenn Sie das Punktprodukt des Vektors von c1 zum Kreis und den Linienvektor und verwenden
    • Stellen Sie sicher, dass die Projektion des Vektors entlang des Linienvektors kürzer ist als die Länge des Liniensegments.

Schritt 7: Implementierung

Hier ist die ActionScript-Implementierung der oben genannten:

 private Funktionsaktualisierung (e: Event): void for (var i: int = 0; i < circles.length; i++)  //calculating line's perpendicular distance to ball var c1_circle:Vector2D = new Vector2D(circles[i].x - x1, circles[i].y - y1); var c1_circle_onNormal:Number = c1_circle.projectionOn(leftNormal); //Att2: getting the relevant vectors var c1_circle_onLine:Number = c1_circle.projectionOn(line); circles[i].y += 2; if ( Math.abs(c1_circle_onNormal) <= circles[i].radius && line.dotProduct(c1_circle) > 0 && c1_circle_onLine < line.getMagnitude() ) //if collision happened, undo movement circles[i].y -= 2;   

Schritt 8: Das Ergebnis

Im Wesentlichen wird das gleiche Ergebnis wie beim vorherigen erzielt, aber da der zweite Ansatz einige Zeilen kürzer ist, denke ich, dass er besser ist.

Fazit

Hoffe das hat geholfen. Danke fürs Lesen. Als nächstes betrachten wir die Kollisionsreaktion.