web-dev-qa-db-fra.com

Trouver un quaternion représentant la rotation d'un vecteur à un autre

J'ai deux vecteurs u et v. Existe-t-il un moyen de trouver un quaternion représentant la rotation de u à v?

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

N'oubliez pas de normaliser q.

Richard a raison de dire qu'il n'y a pas de rotation unique, mais ce qui précède devrait donner "l'arc le plus court", ce qui est probablement ce dont vous avez besoin.

106
Polaris878

Solution vectorielle à mi-chemin

J'ai trouvé la solution que je crois qu'Imbrondir essayait de présenter (bien qu'avec une petite erreur, c'est probablement pourquoi sinisterchipmunk a eu du mal à le vérifier).

Étant donné que nous pouvons construire un quaternion représentant une rotation autour d'un axe comme ceci:

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

Et que le produit scalaire et croisé de deux vecteurs normalisés sont:

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

Étant donné qu'une rotation de à v peut être obtenue en tournant par thêta (l'angle entre les vecteurs) autour du vecteur perpendiculaire, il semble que nous puissions directement construire un quaternion représentant une telle rotation à partir des résultats des produits scalaires et croisés; cependant, en l'état, thêta = angle/2, ce qui signifie que cela entraînerait deux fois la rotation souhaitée.

Une solution consiste à calculer un vecteur à mi-chemin entre et v, et à utiliser le produit scalaire et croisé de et le à mi-chemin vecteur pour construire un quaternion représentant une rotation de deux fois l'angle entre et le - à mi-chemin vecteur, qui nous emmène jusqu'à v!

Il existe un cas particulier, où == -v et un vecteur à mi-chemin unique devient impossible à calculer. Cela est attendu, étant donné l'infiniment de rotations "d'arc le plus court" qui peuvent nous faire passer de à v, et nous devons simplement tourner de 180 degrés autour de tout vecteur orthogonal à - (ou v) comme solution spéciale. Cela se fait en prenant le produit croisé normalisé de avec tout autre vecteur non parallèle à =.

Un pseudo-code suit (évidemment, en réalité, le cas spécial devrait tenir compte des inexactitudes en virgule flottante - probablement en comparant les produits scalaires à un certain seuil plutôt qu'à une valeur absolue).

Notez également qu'il n'y a aucun cas particulier lorsque == v (le quaternion d'identité est produit - vérifiez et voyez pour toi même).

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

La fonction orthogonal renvoie tout vecteur orthogonal au vecteur donné. Cette implémentation utilise le produit croisé avec le vecteur de base le plus orthogonal.

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

Solution de quaternion à mi-chemin

C'est en fait la solution présentée dans la réponse acceptée, et elle semble être légèrement plus rapide que la solution vectorielle à mi-chemin (~ 20% plus rapide par mes mesures, mais ne prenez pas ma parole pour elle). Je l'ajoute ici au cas où d'autres comme moi seraient intéressés par une explication.

Essentiellement, au lieu de calculer un quaternion à l'aide d'un vecteur à mi-chemin, vous pouvez calculer le quaternion qui entraîne deux fois la rotation requise (comme détaillé dans l'autre solution), et trouver le quaternion à mi-chemin entre cela et zéro degré.

Comme je l'ai expliqué précédemment, le quaternion pour le double de la rotation requise est:

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

Et le quaternion pour une rotation nulle est:

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

Le calcul du quaternion à mi-chemin consiste simplement à additionner les quaternions et à normaliser le résultat, tout comme avec les vecteurs. Cependant, comme c'est également le cas avec les vecteurs, les quaternions doivent avoir la même amplitude, sinon le résultat sera biaisé vers le quaternion avec la plus grande amplitude.

Un quaternion construit à partir du produit scalaire et croisé de deux vecteurs aura la même amplitude que ces produits: length(u) * length(v). Plutôt que de diviser les quatre composantes par ce facteur, nous pouvons plutôt augmenter le quaternion identitaire. Et si vous vous demandiez pourquoi la réponse acceptée complique apparemment les choses en utilisant sqrt(length(u) ^ 2 * length(v) ^ 2), c'est parce que la longueur au carré d'un vecteur est plus rapide à calculer que la longueur, afin que nous puissions enregistrer un calcul de sqrt . Le résultat est:

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

Et puis normalisez le résultat. Le pseudo-code suit:

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)));
}
58
Joseph Thomson

Le problème, comme indiqué, n'est pas bien défini: il n'y a pas de rotation unique pour une paire de vecteurs donnée. Considérons le cas, par exemple, où u = <1, 0, 0> et v = <0, 1, 0>. Une rotation de u à v serait une rotation de pi/2 autour de l'axe z. Une autre rotation de u vers v serait une rotation pi autour du vecteur <1, 1, 0>.

5
Richard Dunlap

Pourquoi ne pas représenter le vecteur à l'aide de quaternions purs? Il vaut mieux que vous les normalisiez d'abord peut-être.
q1 = (0 ux uy uz) "
q2 = (0 vx vy vz) "
q1 qpourrir = q2
Pré-multiplier avec q1-1
qpourrir = q1-1 q2
où q1-1 = q1conj / qnorme
Cela peut être considéré comme une "division de gauche". La bonne division, qui n'est pas ce que vous voulez, c'est:
qpourriture, droite = q2-1 q1

4
madratman

Certaines réponses ne semblent pas envisager la possibilité que le produit croisé soit égal à 0. L'extrait ci-dessous utilise une représentation angle-axe:

//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);

toQuaternion peut être implémenté comme suit:

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

Si vous utilisez la bibliothèque Eigen, vous pouvez aussi simplement faire:

Quaternion::FromTwoVectors(from, to)
2
Shital Shah

Je ne suis pas très bon sur Quaternion. Cependant, j'ai lutté pendant des heures à ce sujet et je n'ai pas pu faire fonctionner la solution Polaris878. J'ai essayé de pré-normaliser v1 et v2. Normalisation q. Normaliser q.xyz. Pourtant, je ne comprends toujours pas. Le résultat ne m'a toujours pas donné le bon résultat.

En fin de compte, j'ai trouvé une solution. Si cela aide quelqu'un d'autre, voici mon code de travail (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 )

Un cas particulier doit être fait si v1 et v2 sont parallèles comme v1 == v2 ou v1 == -v2 (avec une certaine tolérance), où je pense que les solutions devraient être Quaternion (1, 0,0,0) (pas de rotation) ou Quaternion (0, * v1) (rotation à 180 degrés)

1
Imbrondir

Du point de vue de l'algorithme, la solution la plus rapide regarde en pseudocode

 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
 }

Assurez-vous que vous avez besoin de quaternions unitaires (généralement, cela est nécessaire pour l'interpolation).

REMARQUE: les quaternions non unitaires peuvent être utilisés avec certaines opérations plus rapidement que l'unité.

0
minorlogic