In meinem vorherigen Tutorial zur Kollisionserkennung zwischen einem Kreis und einer Linie habe ich die Projektion auf einer Linie mit dem Punktprodukt eines Vektors abgedeckt. In diesem Lernprogramm betrachten wir das Produkt mit senkrechten Punkten und verwenden es, um den Schnittpunkt von zwei Linien vorherzusagen.
Werfen wir einen Blick auf das Endergebnis, auf das wir hinarbeiten werden. Verwenden Sie die linke und rechte Pfeiltaste, um das Schiff zu lenken (Dreieck), und drücken Sie auf, um die Geschwindigkeit vorübergehend zu erhöhen. Wenn sich der geplante zukünftige Kollisionspunkt an der Wand befindet (die Linie), wird ein roter Punkt darauf gezeichnet. Bei einer Kollision, die "bereits" vorgekommen ist (d. H. In der Vergangenheit aufgrund der aktuellen Richtung geschehen wäre), wird immer noch ein roter Punkt gezeichnet, jedoch etwas transparent.
Sie können auch die schwarzen Punkte mit der Maus anklicken und ziehen, um die Wand zu verschieben. Beachten Sie, dass wir nicht nur den Ort der Kollision vorhersagen, sondern auch die Zeit.
Bevor wir uns mit dem Thema beschäftigen, lassen Sie uns einige Überarbeitungen vornehmen. Hier ist die Punktproduktgleichung (die zuvor hier behandelt wurde):
Und hier ist die rechtwinklige Produktdefinition aus Wolfram:
Um uns dabei zu helfen, ein mentales Bild zu erstellen, habe ich das Bild unten vorbereitet. Ich bin zuversichtlich, dass Sie an diesem Punkt die vertikalen und horizontalen Komponenten eines Vektors ableiten können, daher sollten die Komponenten, die Sinus und Cosinus umfassen, keine Herausforderung darstellen.
Lassen Sie uns beide Komponenten durch ihre Entsprechung ersetzen. Ich habe A mit einem Hut verwendet, um den Einheitsvektor von A darzustellen (d. H. Einen Vektor, der in die gleiche Richtung wie A zeigt, aber eine Größe von genau 1 hat). Ein weiteres Detail ist, dass das Senkrechte von B tatsächlich die rechte Normale von B ist - mehr zu den Normalen im nächsten Schritt.
Aus dem obigen Diagramm können wir sehen, dass die Projektion von B auf A produzieren wird | B | * cos (Theta)
. Aber warum sollte die Projektion von B normal produzieren | B | * sin (Theta)
?
Um dies besser zu verstehen, habe ich unten eine Flash-Demo hinzugefügt. Klicken Sie auf die schwarze Pfeilspitze und ziehen Sie sie. Wenn Sie es vorsichtig bewegen, werden Sie feststellen, dass die senkrechte Achse ebenfalls folgt. Sobald sie sich drehen, werden auch die fetten roten Linien animiert. Beachten Sie, dass diese beiden Längen gleich sind - daher die Gleichung des senkrechten Punktprodukts.
Normalerweise liegen Normalen auf einer senkrechten Linie, die Ihre interessierende Linie schneidet. Versuchen wir, uns diese Linien auf einer geometrischen Ebene vorzustellen.
Das kartesische Koordinatensystem wird im obigen Diagramm verwendet. B ist das linke Normal und C ist das rechte Normal. Wir sehen, dass die X-Komponente von B negativ ist (weil sie nach links zeigt) und die Y-Komponente von C negativ ist (weil sie nach unten zeigt).
Aber sieh dir die Ähnlichkeiten zwischen B und C an. Ihre X- und Y-Komponenten sind mit denen von A identisch, mit Ausnahme von swizzled. Der Unterschied ist nur die Position des Zeichens. So kommen wir zum Bild unten.
Beachten Sie, dass wir uns speziell auf die Kartesisch Koordinatensystem in diesem Beispiel. Die y-Achse des Flash-Koordinatenraums spiegelt die kartesische Achse wider und führt zu einem Wechsel zwischen der linken und rechten Normalen.
Um den Kollisionspunkt des Vektors k auf der Ebene A herauszufinden, verbinden wir den Schwanz von k mit einem beliebigen Punkt auf der Ebene A zuerst. Für den folgenden Fall ist Vektor j der Verknüpfungsvektor; dann erhalten wir die senkrechte Projektion von k und j auf der Ebene A.
Der rote Punkt im Bild unten ist der Kollisionspunkt. Und ich hoffe, Sie können das ähnliche Dreieck im Diagramm unten sehen.
In Anbetracht der drei oben genannten Komponenten können wir das Verhältnisverhältnis verwenden, um die Länge zwischen den roten und blauen Punkten abzuleiten. Schließlich setzen wir die Größe des Vektors k auf die besagte Länge und haben unseren Kollisionspunkt!
Hier kommt also die ActionScript-Implementierung. Ich habe unten eine Demo hinzugefügt. Versuchen Sie, die Pfeilspitzen so zu verschieben, dass sich beide Linien schneiden. Ein kleiner schwarzer Punkt markiert den Schnittpunkt der Linien. Beachten Sie, dass diese Segmente sich nicht unbedingt schneiden, sondern die unendlichen Linien, die sie darstellen.
Hier ist das Skript, das die Berechnungen durchführt. Auschecken Basic.as
im Quelldownload für das vollständige Skript.
Neuberechnung der privaten Funktion (): void reorient (); / * Erkläre: * v1 & v2 sind Vektoren, die beide Liniensegmente darstellen. * V1 Joins set1b (Endstück) bis Set1a (Kopf) - analog zum Vektor k im Diagramm. * V2 Joinset2b (Endstück) zu Set2a (Kopf) Analog zu Vektor j im Diagramm * / var perp1: Number = v1.perpProduct (v2.normalise ()); var toV2b: Vector2D = neuer Vector2D (set2b.x - set1b.x, set2b.y - set1b.y); var perp2: Number = toV2b.perpProduct (v2.normalise ()); / * Erklärung: * Länge wird aus dem ähnlichen Dreiecksverhältnis berechnet. * Wird später als Größe für einen Vektor verwendet, der in Richtung v1 zeigt. * / Var length: Number = perp2 / perp1 * v1.getMagnitude (); var length_v1: Vector2D = v1.clone (); length_v1.setMagnitude (Länge); / * Explain * Erweitern, um den genauen Ort des Kollisionspunkts zu ermitteln * / intersec.x = set1b.x + length_v1.x; intersec.y = set1b.y + length_v1.y;
Ich hoffe, der erste Ansatz, den ich vorgestellt habe, wurde leicht verstanden. Ich verstehe die Leistung, wenn es darum geht, den Schnittpunkt zu ermitteln, daher werde ich als nächstes alternative Ansätze anbieten, obwohl dies einige mathematische Überarbeitungen erfordert. Tragen Sie mit mir!
Lassen Sie uns zunächst über Liniengleichungen sprechen. Es gibt verschiedene Formen von Liniengleichungen, aber wir werden in diesem Tutorial nur zwei davon berühren:
Ich habe das Bild unten hinzugefügt, damit Sie sich daran erinnern können. Wer sich dafür interessiert, kann sich bei Wikipedia auf diesen Eintrag beziehen.
Bevor wir Manipulationen an zwei Liniengleichungen vornehmen, müssen wir diese Liniengleichungen zuerst herleiten. Betrachten wir das Szenario, in dem wir die Koordinaten zweier Punkte erhalten p1 (a, b)
. und p2 (c, d)
. Wir können eine Liniengleichung bilden, die diese beiden Punkte aus den Gradienten verbindet:
Dann können wir mit dieser Gleichung die Konstanten A, B und C für die Standardform ableiten:
Als nächstes können wir mit dem Lösen simultaner Liniengleichungen fortfahren.
Nun, da wir Liniengleichungen bilden können, nehmen wir zwei Liniengleichungen auf und lösen sie gleichzeitig. In Anbetracht dieser zwei Liniengleichungen:
Ich werde diese Koeffizienten gemäß der allgemeinen Form Ax + By = C aufstellen.
EIN | B | C |
E | F | G |
P | Q | R |
Um den Wert von y zu erhalten, machen wir folgendes:
EIN | B | C | Mal |
E | F | G | P |
P | Q | R | E |
Und wir kommen an der folgenden Tabelle an.
EIN | B | C |
EP | FP | GP |
PE | QE | RE |
Nachdem wir zwei Gleichungen herausgezogen haben, kommen wir zu:
Weiter zu x:
EIN | B | C | Mal |
E | F | G | Q |
P | Q | R | F |
Wir kommen an der folgenden Tabelle an
EIN | B | C |
EQ | FQ | GQ |
PF | QF | RF |
Nachdem wir die beiden Gleichungen herausgezogen haben, kommen wir zu:
Lassen Sie uns y weiter neu anordnen.
So erreichen wir den Schnittpunkt von x und y. Wir stellen fest, dass sie denselben Nenner haben.
Nachdem wir nun die mathematischen Operationen erarbeitet und das Ergebnis erhalten haben, ziehen Sie einfach die Werte ein und wir haben den Schnittpunkt.
Hier ist die Actionscript-Implementierung. Daher sind alle Vektoroperationen auf einfache Arithmetik beschränkt, aber anfangs sind einige Algebra-Operationen erforderlich.
Neuberechnung der privaten Funktion (): void reorient (); var E: Anzahl = set1b.y - set1a.y; var F: Anzahl = set1a.x - set1b.x; var G: Anzahl = set1a.x * set1b.y - set1a.y * set1b.x; var P: Anzahl = set2b.y - set2a.y; var Q: Anzahl = set2a.x - set2b.x; var R: Anzahl = set2a.x * set2b.y - set2a.y * set2b.x; var Nenner: Anzahl = (E * Q - P * F); intersec.x = (G * Q - R * F) / Nenner; intersec.y = (R * E - G * P) / Nenner;
Natürlich ist es das gleiche Ergebnis wie bei der vorherigen Demo, nur mit weniger Rechenaufwand und ohne Verwendung der Vector2D
Klasse.
Eine andere Alternative zur Lösung dieses Problems ist die Verwendung der Matrixmathematik. Ich lade wieder interessierte Leser ein, in den Vortrag von Prof. Wildberger über Gleichungen von Linien einzutauchen. Hier werden wir das Konzept schnell durchblättern.
Laut Prof. Wildberger gibt es zwei Rahmen, die wir übernehmen können:
Lassen Sie uns zuerst die kartesische durchgehen. Schauen Sie sich das Bild unten an.
Beachten Sie, dass die Matrix T und S konstante Werte enthalten. Was noch unbekannt ist, ist A. Die Neuanordnung der Matrixgleichung in A ergibt dann das Ergebnis. Wir müssen jedoch die inverse Matrix von T erhalten.
Hier ist die Implementierung des obigen mit ActionScript:
Neuberechnung der privaten Funktion (): void reorient (); var E: Anzahl = set1b.y - set1a.y; var F: Anzahl = set1a.x - set1b.x; var G: Anzahl = set1a.x * set1b.y - set1a.y * set1b.x; var P: Anzahl = set2b.y - set2a.y; var Q: Anzahl = set2a.x - set2b.x; var R: Anzahl = set2a.x * set2b.y - set2a.y * set2b.x; var T: Matrix = neue Matrix (E, P, F, Q); T.invert (); var S: Matrix = neue Matrix (); S.a = G; S.b = R; S. concat (T); // Multiplizieren der Matrix intersec.x = S.a; intersec.y = S.b;
Schließlich gibt es noch die parametrische Form der Liniengleichung, und wir werden versuchen, sie erneut durch Matrixmathematik zu lösen.
Wir möchten den Schnittpunkt bekommen. Alle Angaben mit Ausnahme von u
und v
die wir zu finden versuchen, werden wir beide Gleichungen in Matrixform umschreiben und lösen.
Wir führen also erneut Matrixmanipulationen durch, um zu unserem Ergebnis zu gelangen.
Also hier ist die Implementierung der Matrixform:
Funktion neu berechnen (): void reorient (); / * Erklären Sie: * r, s beziehen sich eigentlich auf normalisierte Komponenten von v2 * p, q beziehen sich tatsächlich auf normalisierte Komponenten von v1 * / var norm_v2: Vector2D = v2.normalise (); var norm_v1: Vector2D = v1.normalise (); var a_c: Number = set1b.x - set2b.x; var b_d: Number = set1b.y - set2b.y; var R: Matrix = neue Matrix; R.a = norm_v2.x; R. c = norm_v1.x; R.b = norm_v2.y; R.d = norm_v1.y; R.invert (); var L: Matrix = neue Matrix; L a = a_c; Lb = b_d; L.concat (R); intersec.x = set2b.x + L.a * norm_v2.x; intersec.y = set2b.y + L.a * norm_v2.y;
Wir haben vier Ansätze beschrieben, um dieses kleine Problem zu lösen. Was ist mit der Leistung? Nun, ich denke, ich überlasse dieses Thema den Lesern, um es zu beurteilen, obwohl ich glaube, dass der Unterschied vernachlässigbar ist. Fühlen Sie sich frei, diesen Leistungsprüfgurt von Grant Skinner zu verwenden.
Nun, da wir dieses Verständnis haben, was kommt als nächstes? Wende es an!
Angenommen, ein Teilchen bewegt sich in einem Pfad, der mit einer Wand kollidiert. Wir können die Einwirkzeit mit der einfachen Gleichung berechnen:
Geschwindigkeit = Verschiebung / Zeit
Stellen Sie sich vor, Sie befinden sich in diesem orangefarbenen runden Teilchen, und für jedes vorbeiziehende Bild und jede Ankündigung wird die Zeit angegeben, mit der Wand zu kollidieren. Sie hören:
"Aufschlagzeit: 1,5 Bilder" - Bild 1
"Aufschlagzeit: 0,5 Frames" - Frame 2
"Aufprallzeit: -0,5 Bilder" - Bild 3
Wenn wir Frame 3 erreichen, ist bereits eine Kollision mit der Linie aufgetreten (wie durch das negative Vorzeichen angezeigt). Sie müssen die Zeit zurückspulen, um den Kollisionspunkt zu erreichen. Offensichtlich sollte es zu einer Kollision zwischen den Frames 2 und 3 kommen, aber Flash bewegt sich in Schritten von einem Frame. Wenn also eine Kollision in der Mitte zwischen den Bildern stattgefunden hat, zeigt ein Umkehren des Vorzeichens auf ein negatives Vorzeichen an, dass die Kollision bereits stattgefunden hat.
Um eine negative Zeit zu erzielen, verwenden wir das Vektorpunktprodukt. Wir wissen, dass, wenn wir zwei Vektoren haben und die Richtung eines Vektors nicht innerhalb von 90 Grad auf beiden Seiten der anderen liegt, sie ein negatives Punktprodukt erzeugen. Das Punktprodukt ist auch ein Maß dafür, wie parallel zwei Vektoren sind. Wenn also bereits eine Kollision stattgefunden hat, ist die Geschwindigkeit und Richtung eines Vektors zu einem Punkt an der Wand negativ - und umgekehrt.
Also hier ist das Skript (enthalten in CollisionTime.as
). Ich habe hier auch die Kollisionserkennung im Liniensegment hinzugefügt. Für diejenigen, die es nicht kennen, beziehen Sie sich auf mein Tutorial zur Kollisionserkennung zwischen einem Kreis und einem Liniensegment, Schritt 6. Und für die Hilfe bei der Steuerung von Schiffen finden Sie hier eine weitere Referenz.
// Entscheiden, ob innerhalb des Wandsegments var w2_collision: Vector2D = new Vector2D (collision.x - w2.x, collision.y - w2.y); Kollision α = 0; // wenn sich das Schiff links von der Wand befindet if (w2_collision.dotProduct (v1) < 0) t.text = "Ship is heading to left of wall"; else //when ship is heading to right of wall if (w2_collision.getMagnitude() > v1.getMagnitude ()) t.text = "Das Schiff geht rechts von der Wand" //, wenn das Schiff zum Wandsegment geht else var ship_collision: Vector2D = neuer Vector2D (collision.x - ship.x, Kollision). y - ship.y); var Verdrängung: Anzahl = Schiff_Kollision.getMagnitude (); if (ship_collision.dotProduct (velo) < 0) displacement *= -1; //showing text var time:Number = displacement / velo.getMagnitude(); t.text = "Frames to impact: " + time.toPrecision(3) + " frames.\n"; time /= stage.frameRate; t.appendText("Time to impact: " + time.toPrecision(3) + " seconds.\n"); //drop down alpha if collision had happened if (displacement > 0) Kollision α = 1; else Zusammenstoß α = 0,5; t.appendText ("Kollision war bereits passiert.")
Hier ist eine Demo von dem, worauf Sie ankommen werden. Verwenden Sie die linke und rechte Pfeiltaste, um das Schiff zu lenken (Dreieck), und drücken Sie Up, um die Geschwindigkeit vorübergehend zu erhöhen. Wenn der vorhergesagte zukünftige Kollisionspunkt an der Wand (der Linie) liegt, wird ein roter Punkt darauf gezeichnet. Bei einer Kollision, die "bereits" stattgefunden hat, wird noch ein roter Punkt gezeichnet, jedoch etwas transparent. Sie können die schwarzen Punkte auch auf beiden Seiten der Wand ziehen, um sie zu verschieben.
Ich hoffe also, dass dieses Tutorial informativ war. Teilen Sie uns mit, wenn Sie diese Idee an anderer Stelle von der oben genannten umgesetzt haben. Ich plane einen kurzen Bericht über die Anwendung von Lasersystemen - was denken Sie? Vielen Dank für das Lesen und lassen Sie mich wissen, wenn es Fehler gibt.