Finden einer Quaternion, die die Rotation von einem Vektor zu einem anderen darstellt

105

Ich habe zwei Vektoren u und v. Gibt es eine Möglichkeit, ein Quaternion zu finden, das die Rotation von u nach v darstellt?

sdfqwerqaz1
quelle

Antworten:

115
Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);

Vergessen Sie nicht, q zu normalisieren.

Richard hat Recht damit, dass es keine eindeutige Rotation gibt, aber das Obige sollte den "kürzesten Bogen" ergeben, was wahrscheinlich das ist, was Sie brauchen.

Polaris878
quelle
30
Beachten Sie, dass dies nicht für parallele Vektoren gilt (beide in die gleiche Richtung oder in entgegengesetzte Richtungen). crossproductist in diesen Fällen nicht gültig, daher müssen Sie zuerst überprüfen dot(v1, v2) > 0.999999und dot(v1, v2) < -0.999999entweder ein Identitätsquat für parallele Vektoren zurückgeben oder eine 180-Grad-Drehung (um eine beliebige Achse) für entgegengesetzte Vektoren zurückgeben.
Sinisterchipmunk
11
Eine gute Implementierung davon finden Sie im ogre3d-Quellcode
João Portela
4
@sinisterchipmunk Wenn v1 = v2 wäre, wäre das Kreuzprodukt (0,0,0) und w wäre positiv, was sich zur Identität normalisiert. Laut gamedev.net/topic/… sollte es auch für v1 = -v2 und in deren unmittelbarer Nähe gut funktionieren.
JPA
3
Wie hat jemand diese Technik zum Laufen gebracht? Zum einen sqrt((v1.Length ^ 2) * (v2.Length ^ 2))vereinfacht sich v1.Length * v2.Length. Ich konnte keine Variation davon bekommen, um vernünftige Ergebnisse zu erzielen.
Joseph Thomson
2
Ja, das funktioniert. Siehe Quellcode . L61 behandelt, ob die Vektoren in entgegengesetzte Richtungen weisen (PI zurückgeben, andernfalls würde die Identität gemäß der Bemerkung von @ jpa zurückgegeben). L67 verarbeitet parallele Vektoren: mathematisch unnötig, aber schneller. L72 ist die Antwort von Polaris878, vorausgesetzt, beide Vektoren haben eine Einheitslänge (vermeidet ein Quadrat). Siehe auch Unit-Tests .
sinisterchipmunk
63

Half-Way-Vektorlösung

Ich kam auf die Lösung, die Imbrondir meiner Meinung nach zu präsentieren versuchte (wenn auch mit einem kleinen Fehler, weshalb Sinisterchipmunk wahrscheinlich Probleme hatte, sie zu überprüfen).

Vorausgesetzt, wir können ein Quaternion konstruieren, das eine Drehung um eine Achse wie folgt darstellt:

q.w == cos(angle / 2)
q.x == sin(angle / 2) * axis.x
q.y == sin(angle / 2) * axis.y
q.z == sin(angle / 2) * axis.z

Und dass das Punkt- und Kreuzprodukt zweier normalisierter Vektoren sind:

dot     == cos(theta)
cross.x == sin(theta) * perpendicular.x
cross.y == sin(theta) * perpendicular.y
cross.z == sin(theta) * perpendicular.z

Da eine Drehung von u nach v durch Drehen um Theta (den Winkel zwischen den Vektoren) um den senkrechten Vektor erreicht werden kann, sieht es so aus, als könnten wir aus den Ergebnissen der Punkt- und Kreuzprodukte direkt eine Quaternion konstruieren, die eine solche Drehung darstellt ;; So wie es aussieht, ist Theta = Winkel / 2 , was bedeutet, dass dies zu der doppelten gewünschten Drehung führen würde.

Eine Lösung besteht darin, einen Vektor auf halbem Weg zwischen u und v zu berechnen und das Punkt- und Kreuzprodukt von u und dem halben Vektor zu verwenden, um eine Quaternion zu konstruieren, die eine Drehung um den doppelten Winkel zwischen u und dem halben Vektor darstellt. das bringt uns den ganzen Weg nach v !

Es gibt einen Sonderfall, in dem u == -v und ein eindeutiger Halbwertsvektor nicht mehr berechnet werden können. Dies wird angesichts der unendlich vielen "kürzesten Bogen" -Drehungen erwartet, die uns von u nach v führen können , und wir müssen uns einfach um 180 Grad um jeden Vektor drehen, der orthogonal zu u (oder v ) ist, als unsere Sonderfalllösung. Dies geschieht, indem das normalisierte Kreuzprodukt von u mit einem anderen Vektor genommen wird, der nicht parallel zu u ist .

Es folgt ein Pseudocode (in der Realität müsste der Sonderfall natürlich Gleitkomma-Ungenauigkeiten berücksichtigen - wahrscheinlich durch Überprüfen der Punktprodukte anhand eines bestimmten Schwellenwerts anstelle eines absoluten Werts).

Beachten Sie auch, dass es keinen Sonderfall gibt, wenn u == v (die Identitätsquaternion wird erstellt - überprüfen Sie und überzeugen Sie sich selbst).

// N.B. the arguments are _not_ axis and angle, but rather the
// raw scalar-vector components.
Quaternion(float w, Vector3 xyz);

Quaternion get_rotation_between(Vector3 u, Vector3 v)
{
  // It is important that the inputs are of equal length when
  // calculating the half-way vector.
  u = normalized(u);
  v = normalized(v);

  // Unfortunately, we have to check for when u == -v, as u + v
  // in this case will be (0, 0, 0), which cannot be normalized.
  if (u == -v)
  {
    // 180 degree rotation around any orthogonal vector
    return Quaternion(0, normalized(orthogonal(u)));
  }

  Vector3 half = normalized(u + v);
  return Quaternion(dot(u, half), cross(u, half));
}

Die orthogonalFunktion gibt jeden Vektor zurück, der orthogonal zum angegebenen Vektor ist. Diese Implementierung verwendet das Kreuzprodukt mit dem orthogonalsten Basisvektor.

Vector3 orthogonal(Vector3 v)
{
    float x = abs(v.x);
    float y = abs(v.y);
    float z = abs(v.z);

    Vector3 other = x < y ? (x < z ? X_AXIS : Z_AXIS) : (y < z ? Y_AXIS : Z_AXIS);
    return cross(v, other);
}

Quaternion-Lösung auf halbem Weg

Dies ist tatsächlich die Lösung, die in der akzeptierten Antwort dargestellt ist, und sie scheint geringfügig schneller zu sein als die Vektorlösung auf halbem Weg (~ 20% schneller durch meine Messungen, obwohl ich nicht mein Wort dafür nehme). Ich füge es hier hinzu, falls andere wie ich an einer Erklärung interessiert sind.

Anstatt ein Quaternion mit einem Halbwertsvektor zu berechnen, können Sie im Wesentlichen das Quaternion berechnen, das zu der doppelten erforderlichen Rotation führt (wie in der anderen Lösung beschrieben), und das Quaternion auf halbem Weg zwischen diesem und null Grad ermitteln.

Wie ich zuvor erklärt habe, lautet die Quaternion für die doppelte erforderliche Rotation:

q.w   == dot(u, v)
q.xyz == cross(u, v)

Und die Quaternion für die Nullrotation lautet:

q.w   == 1
q.xyz == (0, 0, 0)

Die Berechnung der Quaternion auf halbem Weg ist einfach eine Frage der Summierung der Quaternionen und der Normalisierung des Ergebnisses, genau wie bei Vektoren. Wie auch bei Vektoren müssen die Quaternionen jedoch dieselbe Größe haben, da sonst das Ergebnis in Richtung der Quaternion mit der größeren Größe verschoben wird.

Ein Quaternion, das aus dem Punkt- und Kreuzprodukt zweier Vektoren aufgebaut ist, hat die gleiche Größe wie diese Produkte : length(u) * length(v). Anstatt alle vier Komponenten durch diesen Faktor zu teilen, können wir stattdessen die Identitätsquaternion vergrößern. Und wenn Sie sich gefragt haben, warum die akzeptierte Antwort die Sache scheinbar kompliziert macht sqrt(length(u) ^ 2 * length(v) ^ 2), liegt dies daran, dass die quadratische Länge eines Vektors schneller zu berechnen ist als die Länge, sodass wir eine sqrtBerechnung speichern können . Das Ergebnis ist:

q.w   = dot(u, v) + sqrt(length_2(u) * length_2(v))
q.xyz = cross(u, v)

Und dann normalisieren Sie das Ergebnis. Pseudocode folgt:

Quaternion get_rotation_between(Vector3 u, Vector3 v)
{
  float k_cos_theta = dot(u, v);
  float k = sqrt(length_2(u) * length_2(v));

  if (k_cos_theta / k == -1)
  {
    // 180 degree rotation around any orthogonal vector
    return Quaternion(0, normalized(orthogonal(u)));
  }

  return normalized(Quaternion(k_cos_theta + k, cross(u, v)));
}
Joseph Thomson
quelle
12
+1: Großartig! Dies wirkte wie ein Zauber. Sollte die akzeptierte Antwort sein.
Rekin
1
Die Quaternion-Syntax ist in einigen Beispielen aktiviert (Quaternion (xyz, w) und Quaternion (w, xyz)). Es scheint auch, dass im letzten Codeblock Bogenmaß und Grad gemischt werden, um Winkel auszudrücken (180 vs. k_cos_theta + k).
Guillermo Blasco
1
Quaternion (float, Vector3) ist eine Konstruktion aus einem Skalarvektor, während Quaternion (Vector3, float) eine Konstruktion aus einem Achsenwinkel ist. Vielleicht verwirrend, aber ich denke, es ist richtig. Korrigieren Sie mich, wenn Sie immer noch denken, dass es falsch ist!
Joseph Thomson
Es funktionierte! Vielen Dank! Ich fand jedoch einen anderen ähnlichen und gut erklärten Link , um die obige Operation durchzuführen. Ich dachte, ich sollte für die Aufzeichnung teilen;)
Sünder
1
@JosephThomson Die Lösung für die Quaternion auf halbem Weg scheint von hier zu kommen .
Legends2k
6

Das angegebene Problem ist nicht genau definiert: Es gibt keine eindeutige Drehung für ein bestimmtes Vektorpaar. Betrachten Sie zum Beispiel den Fall, in dem u = <1, 0, 0> und v = <0, 1, 0> . Eine Umdrehung von u nach v wäre eine pi / 2- Umdrehung um die z-Achse. Eine andere Drehung von u nach v wäre eine pi- Drehung um den Vektor <1, 1, 0> .

Richard Dunlap
quelle
1
Gibt es nicht unendlich viele mögliche Antworten? Denn nachdem Sie den Vektor "von" mit dem Vektor "nach" ausgerichtet haben, können Sie das Ergebnis immer noch frei um seine Achse drehen? Wissen Sie, welche zusätzlichen Informationen normalerweise verwendet werden können, um diese Auswahl einzuschränken und das Problem klar zu definieren?
Doug McClean
5

Warum nicht den Vektor mit reinen Quaternionen darstellen? Es ist besser, wenn Sie sie vielleicht zuerst normalisieren.
q 1 = (0 u x u y u z ) '
q 2 = (0 v x v y v z )'
q 1 q rot = q 2
Vormultiplizieren mit q 1 -1
q rot = q 1 -1 q 2
wobei q 1 -1 = q 1 konj / q Norm
Dies kann als "linke Teilung" angesehen werden. Die rechte Teilung, die Sie nicht wollen, ist:
q rot, rechts = q 2 -1 q 1

Madratman
quelle
2
Ich bin verloren, ist die Rotation von q1 nach q2 nicht berechnet als q_2 = q_rot q_1 q_rot ^ -1?
Yota
4

Ich bin nicht sehr gut in Quaternion. Ich hatte jedoch stundenlang Probleme damit und konnte die Polaris878-Lösung nicht zum Laufen bringen. Ich habe versucht, v1 und v2 vornormalisieren. Normalisierung q. Normalisierung von q.xyz. Trotzdem verstehe ich es nicht. Das Ergebnis hat mir immer noch nicht das richtige Ergebnis gebracht.

Am Ende fand ich jedoch eine Lösung, die es tat. Wenn es jemand anderem hilft, ist hier mein Arbeitscode (Python):

def diffVectors(v1, v2):
    """ Get rotation Quaternion between 2 vectors """
    v1.normalize(), v2.normalize()
    v = v1+v2
    v.normalize()
    angle = v.dot(v2)
    axis = v.cross(v2)
    return Quaternion( angle, *axis )

Ein Sonderfall muss gemacht werden, wenn v1 und v2 wie v1 == v2 oder v1 == -v2 (mit einer gewissen Toleranz) parallel sind, wobei ich glaube, dass die Lösungen Quaternion (1, 0,0,0) sein sollten (keine Rotation) oder Quaternion (0, * v1) (180-Grad-Drehung)

Imbrondir
quelle
Ich habe eine funktionierende Implementierung, aber Ihre ist hübscher, also wollte ich wirklich, dass sie funktioniert. Leider sind alle meine Testfälle fehlgeschlagen. Meine Tests sehen alle ungefähr so ​​aus quat = diffVectors(v1, v2); assert quat * v1 == v2.
Sinisterchipmunk
Es ist unwahrscheinlich, dass dies überhaupt funktioniert, da es angleseinen Wert von einem Punktprodukt erhält.
Sam Hocevar
Wo ist die Quaternion () -Funktion?
Juni Wang
3

Einige der Antworten scheinen die Möglichkeit nicht in Betracht zu ziehen, dass das Kreuzprodukt 0 sein könnte. Unter dem Snippet wird die Winkelachsendarstellung verwendet:

//v1, v2 are assumed to be normalized
Vector3 axis = v1.cross(v2);
if (axis == Vector3::Zero())
    axis = up();
else
    axis = axis.normalized();

return toQuaternion(axis, ang);

Das toQuaternionkann wie folgt implementiert werden:

static Quaternion toQuaternion(const Vector3& axis, float angle)
{
    auto s = std::sin(angle / 2);
    auto u = axis.normalized();
    return Quaternion(std::cos(angle / 2), u.x() * s, u.y() * s, u.z() * s);
}

Wenn Sie die Eigenbibliothek verwenden, können Sie auch einfach Folgendes tun:

Quaternion::FromTwoVectors(from, to)
Shital Shah
quelle
toQuaternion(axis, ang)-> Sie haben vergessen anzugeben, was istang
Maksym Ganenko
Der zweite Parameter ist angleTeil der Achsenwinkeldarstellung des Quaternions, gemessen im Bogenmaß.
Shital Shah
Sie wurden gebeten, die Quaternion dazu zu bringen, sich von einem Vektor zum anderen zu drehen. Sie haben keinen Winkel, Sie müssen ihn zuerst berechnen. Ihre Antwort sollte die Berechnung des Winkels enthalten. Prost!
Maksym Ganenko
Das ist c ++? Was ist UX ()?
Juni Wang
Ja, das ist C ++. u ist ein Vektortyp aus der Eigenbibliothek (falls Sie einen verwenden).
Shital Shah
2

Aus Sicht des Algorithmus sieht die schnellste Lösung im Pseudocode aus

 Quaternion shortest_arc(const vector3& v1, const vector3& v2 ) 
 {
     // input vectors NOT unit
     Quaternion q( cross(v1, v2), dot(v1, v2) );
     // reducing to half angle
     q.w += q.magnitude(); // 4 multiplication instead of 6 and more numerical stable

     // handling close to 180 degree case
     //... code skipped 

        return q.normalized(); // normalize if you need UNIT quaternion
 }

Stellen Sie sicher, dass Sie Einheitenquaternionen benötigen (normalerweise ist dies für die Interpolation erforderlich).

HINWEIS: Nicht-Einheiten-Quaternionen können bei einigen Vorgängen schneller als Einheiten verwendet werden.

minorlogic
quelle