Direkte Methode zur Berechnung des Winkels im Uhrzeigersinn zwischen 2 Vektoren


78

Ich möchte den Winkel im Uhrzeigersinn zwischen 2 Vektoren (2D, 3D) herausfinden.

Der klassische Weg mit dem Punktprodukt gibt mir den Innenwinkel (0-180 Grad) und ich muss einige if-Anweisungen verwenden, um zu bestimmen, ob das Ergebnis der Winkel ist, den ich brauche, oder dessen Komplement.

Kennen Sie eine direkte Methode zur Berechnung des Winkels im Uhrzeigersinn?


6
Warum nicht verwenden std::atan2()?

2
Wie definieren Sie "Winkel im Uhrzeigersinn" für Vektoren in 3D?
Martin R

@ H2CO3 Dies scheint die beste Lösung für 2D-Winkel zu sein.
Mircea Ispas

@MartinR "im Uhrzeigersinn" ist ein Oberbegriff dafür, dass ich den Winkel in einer bestimmten "Richtung" und nicht in der nächsten "Richtung" haben möchte. Nickolay O. spezifizierte in seiner Antwort eine Art, diese "Richtung" zu beschreiben
Mircea Ispas

4
@Felics: "im Uhrzeigersinn" ist in 2D gut definiert, aber nicht in 3D. Das Überprüfen der z-Koordinate des Kreuzprodukts (wie in der Antwort von Nickolay O.) würde in 3D bedeuten: "Im Uhrzeigersinn für einen Beobachter, der von oben auf die x / y-Ebene schaut."
Martin R

Antworten:


189

2D-Fall

So wie das Punktprodukt proportional zum Kosinus des Winkels ist, ist die Determinante proportional zu seinem Sinus. Sie können den Winkel also folgendermaßen berechnen:

dot = x1*x2 + y1*y2      # dot product between [x1, y1] and [x2, y2]
det = x1*y2 - y1*x2      # determinant
angle = atan2(det, dot)  # atan2(y, x) or atan2(sin, cos)

Die Ausrichtung dieses Winkels entspricht der des Koordinatensystems. In einem linkshändigen Koordinatensystem , dh x zeigt nach rechts und y nach unten, wie es für Computergrafiken üblich ist, bedeutet dies, dass Sie ein positives Vorzeichen für Winkel im Uhrzeigersinn erhalten. Wenn die Ausrichtung des Koordinatensystems mit y nach oben mathematisch ist, erhalten Sie Winkel gegen den Uhrzeigersinn, wie es in der Mathematik üblich ist. Durch Ändern der Reihenfolge der Eingaben wird das Vorzeichen geändert. Wenn Sie mit den Vorzeichen nicht zufrieden sind, tauschen Sie einfach die Eingaben aus.

3D-Fall

In 3D definieren zwei beliebig platzierte Vektoren ihre eigene Rotationsachse senkrecht zu beiden. Diese Drehachse hat keine feste Ausrichtung, was bedeutet, dass Sie die Richtung des Drehwinkels auch nicht eindeutig festlegen können. Eine übliche Konvention besteht darin, Winkel immer positiv sein zu lassen und die Achse so auszurichten, dass sie zu einem positiven Winkel passt. In diesem Fall reicht das Punktprodukt der normalisierten Vektoren aus, um Winkel zu berechnen.

dot = x1*x2 + y1*y2 + z1*z2    #between [x1, y1, z1] and [x2, y2, z2]
lenSq1 = x1*x1 + y1*y1 + z1*z1
lenSq2 = x2*x2 + y2*y2 + z2*z2
angle = acos(dot/sqrt(lenSq1 * lenSq2))

Ebene in 3D eingebettet

Ein Sonderfall ist der Fall, in dem Ihre Vektoren nicht willkürlich platziert werden, sondern in einer Ebene mit einem bekannten Normalenvektor n liegen . Dann liegt die Rotationsachse ebenfalls in Richtung n , und die Ausrichtung von n legt eine Ausrichtung für diese Achse fest. In diesem Fall können Sie die obige 2D-Berechnung, einschließlich n, in die Determinante anpassen , um ihre Größe 3 × 3 zu erhalten.

dot = x1*x2 + y1*y2 + z1*z2
det = x1*y2*zn + x2*yn*z1 + xn*y1*z2 - z1*y2*xn - z2*yn*x1 - zn*y1*x2
angle = atan2(det, dot)

Eine Bedingung dafür ist, dass der normale Vektor n eine Längeneinheit hat. Wenn nicht, müssen Sie es normalisieren.

Als dreifaches Produkt

Diese Determinante könnte auch als dreifaches Produkt ausgedrückt werden , wie @Excrubulent in einer vorgeschlagenen Bearbeitung hervorhob .

det = n · (v1 × v2)

Dies ist möglicherweise in einigen APIs einfacher zu implementieren und bietet eine andere Perspektive auf das, was hier vor sich geht: Das Kreuzprodukt ist proportional zum Sinus des Winkels und liegt senkrecht zur Ebene, also ein Vielfaches von n . Das Punktprodukt misst daher grundsätzlich die Länge dieses Vektors, jedoch mit dem richtigen Vorzeichen.


4
Haben Sie eine positive Bewertung - ich kann mir nicht die Mühe machen herauszufinden, ob die anderen Antworten richtig sind oder nicht. Ihre ist die klarste und am besten lesbare, also hat sie mir geholfen.
Excrubulent

2
Für die 2D bekomme ich (0,180) und (-180,0). Man kann überprüfen, ob das Ergebnis negativ ist, und 360 hinzufügen, um einen schönen Winkel im Uhrzeigersinn zu erhalten (zum Beispiel wenn -180 360 Ergebnisse in 180 addiert, -90 360 Ergebnisse in 270 usw.). Ich weiß nicht, ob es nur meine Berechnung oder die Implementierung des qAtan2(y, x)(aus dem Qt-Framework) ist, aber wenn jemand das gleiche Problem wie ich hat, könnte dies helfen.
Rbaleksandar

8
@rbaleksandar: liegt atan2normalerweise im Bereich [-180 °, 180 °]. Um [0 °, 360 °] ohne Fallunterscheidung kann man ersetzen atan2(y,x)mit atan2(-y,-x) + 180°.
MvG

1
@jianz: Der Winkel ist der positive Winkel zum Koordinatensystem. Wenn x richtig und y oben ist, ist der Winkel gegen den Uhrzeigersinn. Wenn y unten ist, ist es im Uhrzeigersinn. Die meisten Computergrafikumgebungen verwenden Letzteres. Wenn Sie die Ausrichtung umkehren möchten, ändern Sie einfach die Reihenfolge der Eingaben, wodurch das Vorzeichen der Determinante umgedreht wird.
MvG

3
Noooooo nimm niemals Acos von einem Punktprodukt! Das ist mathematisch korrekt, aber in der Praxis schrecklich ungenau. Sie können Ihre 3D-Methode durch eine andere atan2 (det, dot) ersetzen. in diesem Fall wäre det die Länge des Kreuzprodukts.
Don Hatch

5

Um den Winkel zu berechnen, müssen Sie nur den atan2(v1.s_cross(v2), v1.dot(v2))2D-Fall aufrufen . Wo s_crossist das skalare Analogon der Kreuzproduktion (signierter Bereich des Parallelogramms)? Für 2D-Fälle wäre das Keilproduktion. Für 3D-Fälle müssen Sie die Drehung im Uhrzeigersinn definieren, da von einer Seite der Ebene im Uhrzeigersinn eine Richtung und von der anderen Seite der Ebene eine andere Richtung ist =)

Bearbeiten: Dies ist der Winkel gegen den Uhrzeigersinn, der Winkel im Uhrzeigersinn ist genau entgegengesetzt


v1.cross (v2) ist ein Vektor, kein Skalar und kann nicht so verwendet werden. Nickolay O. beschreibt in seiner Antwort, wie man die Richtung des Winkels herausfindet. Eine Möglichkeit, einen 2D-Winkel zu erhalten, ist: angle = atan2f (v2.x, v2.y) - atan2f (v1.x, v1.y)
Mircea Ispas

1
@Felics In der 2D-Kreuzproduktion bedeutet häufig Keilproduktion en.wikipedia.org/wiki/Wedge_product Das ist ein signierter Bereich des Parallelogramms. Für den 2D-Fall ist diese Formel absolut korrekt, da sie dot = | v1 || v2 | * cos und cross = | v1 || v2 | sin ist. Deshalb gibt atan2 den korrekten Winkel im gesamten Kreisbereich an. Und wie ich für 3D-Fall sagte, müssen Sie einige Annahmen treffen, um eine gewisse Erweiterung der Ausrichtung im Uhrzeigersinn zu haben
Kassak

1
@Felics: Beachten Sie, dass atan2fdie y-Koordinate das erste Argument ist, also sollte es so sein angle = atan2f(v2.y, v2.x) - atan2f(v1.y, v1.x).
Martin R

1
@kassak: Sie könnten ersetzen crossund dotdurch die explizite Formel im 2D-Fall alle Zweifel an der crossRückgabe eines 3D-Vektors beseitigen (aber das ist nur ein Vorschlag, den Sie ignorieren können). - Ansonsten gefällt mir diese Lösung, weil sie nur einen atan2fFunktionsaufruf erfordert .
Martin R

@ Martin R danke für den guten Rat. Ich habe einige Korrekturen vorgenommen, um die Bedeutung der Formel klarer zu machen
Kassak

4

Diese Antwort ist dieselbe wie die von MvG, erklärt sie jedoch anders (sie ist das Ergebnis meiner Bemühungen, zu verstehen, warum die Lösung von MvG funktioniert). Ich poste es mit der Möglichkeit, dass andere es hilfreich finden.

Der Winkel gegen den Uhrzeigersinn thetavon xbis yin Bezug auf den Blickwinkel ihrer gegebenen Normalen n( ||n|| = 1) ist gegeben durch

atan2 (Punkt (n, Kreuz (x, y)), Punkt (x, y))

(1) = atan2 (|| x || || y || sin (Theta), || x || || y || cos (Theta))

(2) = atan2 (sin (Theta), cos (Theta))

(3) = Winkel gegen den Uhrzeigersinn zwischen der x-Achse und dem Vektor (cos (Theta), sin (Theta))

(4) = Theta

wobei ||x||die Größe von bezeichnet x.

Schritt (1) folgt mit der Feststellung, dass

Kreuz (x, y) = || x || || y || sin (Theta) n,

und so

Punkt (n, Kreuz (x, y))

= Punkt (n, || x || || y || sin (Theta) n)

= || x || || y || sin (Theta) Punkt (n, n)

was gleich ist

|| x || || y || Sünde (Theta)

wenn ||n|| = 1.

Schritt (2) folgt aus der Definition von atan2, wobei zu beachten ist atan2(cy, cx) = atan2(y,x), wo cein Skalar ist. Schritt (3) folgt aus der Definition von atan2. Schritt (4) folgt aus den geometrischen Definitionen von cosund sin.


2

Mit dem Skalarprodukt (Punktprodukt) zweier Vektoren können Sie den Cosinus des Winkels zwischen ihnen ermitteln. Um die 'Richtung' des Winkels zu erhalten, sollten Sie auch das Kreuzprodukt berechnen, mit dem Sie überprüfen können (über die z-Koordinate), ob der Winkel im Uhrzeigersinn ist oder nicht (dh ob Sie ihn aus 360 Grad extrahieren oder nicht).


1
Auch wenn dies richtig ist, möchte ich dies vermeiden - um einen Wert zu berechnen und festzustellen, ob der berechnete Wert meinen Winkel oder das Komplement meines Winkels darstellt.
Mircea Ispas

Ich möchte wissen, ob dies möglich ist :) Warum sollte man eine unzulängliche Methode anwenden, wenn es (vielleicht!) Eine bessere Methode gibt? Wenn es keinen besseren Weg gibt, werde ich das "Standard" -Ding, aber es ist immer gut, nach Besserem zu fragen!
Mircea Ispas

Eigentlich Standardwege nicht immer effizient)
Nickolay

@NickolayOlshevsky Was meinst du genau mit Check via Z-Koordinate , wie kann ich das machen?
Ogen

Soweit ich mich erinnere, sollten Sie das Vorzeichen der Z-Koordinate überprüfen.
Nickolay Olshevsky

1

Für eine 2D-Methode können Sie das Kosinusgesetz und die Richtungsmethode verwenden.

Berechnung des Winkels des Segments P3: P1 im Uhrzeigersinn zum Segment P3: P2.

 
    P1 P2

        P3
    double d = direction(x3, y3, x2, y2, x1, y1);

    // c
    int d1d3 = distanceSqEucl(x1, y1, x3, y3);

    // b
    int d2d3 = distanceSqEucl(x2, y2, x3, y3);

    // a
    int d1d2 = distanceSqEucl(x1, y1, x2, y2);

    //cosine A = (b^2 + c^2 - a^2)/2bc
    double cosA = (d1d3 + d2d3 - d1d2)
        / (2 * Math.sqrt(d1d3 * d2d3));

    double angleA = Math.acos(cosA);

    if (d > 0) {
        angleA = 2.*Math.PI - angleA;
    }

This has the same number of transcendental

Operationen als obige Vorschläge und nur eine weitere Gleitkommaoperation.

Die verwendeten Methoden sind:

 public int distanceSqEucl(int x1, int y1, 
    int x2, int y2) {

    int diffX = x1 - x2;
    int diffY = y1 - y2;
    return (diffX * diffX + diffY * diffY);
}

public int direction(int x1, int y1, int x2, int y2, 
    int x3, int y3) {

    int d = ((x2 - x1)*(y3 - y1)) - ((y2 - y1)*(x3 - x1));

    return d;
}

0

Wenn Sie mit "direktem Weg" das Vermeiden der ifAussage meinen , dann glaube ich nicht, dass es eine wirklich allgemeine Lösung gibt.

Wenn Ihr spezifisches Problem jedoch den Verlust einer gewissen Genauigkeit bei der Winkeldiskretisierung zulässt und Sie einige Zeit bei der Typkonvertierung verlieren können, können Sie den zulässigen Bereich des Phi-Winkels [-pi, pi) auf den zulässigen Bereich eines vorzeichenbehafteten Ganzzahltyps abbilden . Dann würden Sie die Komplementarität kostenlos erhalten. Allerdings habe ich diesen Trick in der Praxis nicht wirklich angewendet. Höchstwahrscheinlich würden die Kosten für Float-to-Integer- und Integer-to-Float-Konvertierungen den Nutzen der Direktheit überwiegen. Es ist besser, Ihre Prioritäten beim Schreiben von autovektorisierbarem oder parallelisierbarem Code festzulegen, wenn diese Winkelberechnung häufig durchgeführt wird.

Wenn Ihre Problemdetails so sind, dass es ein definitiv wahrscheinlicheres Ergebnis für die Winkelrichtung gibt, können Sie die integrierten Funktionen des Compilers verwenden, um diese Informationen an den Compiler weiterzuleiten und so die Verzweigung effizienter zu optimieren. Zum Beispiel im Fall von gcc ist das eine __builtin_expectFunktion. Es ist etwas praktischer, wenn Sie es in solche likelyund unlikelyMakros einwickeln (wie im Linux-Kernel):

#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)

-1

Eine Formel für den Winkel im Uhrzeigersinn, 2D-Fall, zwischen 2 Vektoren, xa, ya und xb, yb.

Winkel (vec.a-vec, b) = pi () / 2 * ((1 + Zeichen (ya)) * (1-Zeichen (xa ^ 2)) - (1 + Zeichen (yb)) * (1- Zeichen (xb ^ 2)))

                        +pi()/4*((2+sign(ya))*sign(xa)-(2+sign(yb))*sign(xb))

                        +sign(xa*ya)*atan((abs(ya)-abs(xa))/(abs(ya)+abs(xa)))

                        -sign(xb*yb)*atan((abs(yb)-abs(xb))/(abs(yb)+abs(xb)))

-2

Kopieren Sie diese einfach und fügen Sie sie ein.

angle = (acos((v1.x * v2.x + v1.y * v2.y)/((sqrt(v1.x*v1.x + v1.y*v1.y) * sqrt(v2.x*v2.x + v2.y*v2.y))))/pi*180);

Bitte ;-)


5
Obwohl dieses Code-Snippet möglicherweise die Frage beantwortet, einschließlich einer Erklärung, warum und wie es zur Lösung des Problems beiträgt, verbessert es die Qualität und Langlebigkeit Ihrer Antwort, insbesondere in Bezug auf ältere Fragen wie diese. Siehe "Wie schreibe ich eine gute Antwort?" .
träge
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.