Trovare il quaternione che rappresenta la rotazione da un vettore all'altro


105

Ho due vettori ue v. C'è un modo per trovare un quaternione che rappresenta la rotazione da u a v?

Risposte:


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

Non dimenticare di normalizzare q.

Richard ha ragione sul fatto che non ci sia una rotazione unica, ma quanto sopra dovrebbe fornire l '"arco più corto", che è probabilmente quello di cui hai bisogno.


30
Tieni presente che questo non gestisce il caso di vettori paralleli (entrambi nella stessa direzione o che puntano in direzioni opposte). crossproductnon sarà valido in questi casi, quindi devi prima controllare dot(v1, v2) > 0.999999e dot(v1, v2) < -0.999999, rispettivamente, e restituire un quat di identità per i vettori paralleli, oppure restituire una rotazione di 180 gradi (su qualsiasi asse) per i vettori opposti.
sinisterchipmunk

11
Una buona implementazione di questo può essere trovata nel codice sorgente ogre3d
João Portela

4
@sinisterchipmunk In realtà, se v1 = v2, il prodotto incrociato sarebbe (0,0,0) ew sarebbe positivo, il che si normalizza all'identità. Secondo gamedev.net/topic/… dovrebbe funzionare bene anche per v1 = -v2 e nelle loro immediate vicinanze.
jpa

3
Come ha fatto qualcuno a far funzionare questa tecnica? Per uno, sqrt((v1.Length ^ 2) * (v2.Length ^ 2))semplifica a v1.Length * v2.Length. Non ho potuto ottenere alcuna variazione di questo per produrre risultati sensati.
Joseph Thomson

2
Sì, funziona. Vedi codice sorgente . L61 gestisce se i vettori affrontano direzioni opposte (restituisce PI, altrimenti restituirebbe identità per l'osservazione di @ jpa). L67 gestisce i vettori paralleli: matematicamente non necessario, ma più veloce. L72 è la risposta di Polaris878, assumendo che entrambi i vettori siano lunghezza unitaria (evita un sqrt). Vedi anche unit test .
sinisterchipmunk

63

Soluzione vettoriale a metà strada

Ho trovato la soluzione che credo che Imbrondir stesse cercando di presentare (anche se con un piccolo errore, che probabilmente era il motivo per cui sinisterchipmunk aveva problemi a verificarlo).

Dato che possiamo costruire un quaternione che rappresenta una rotazione attorno a un asse in questo modo:

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

E che il punto e il prodotto incrociato di due vettori normalizzati sono:

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

Visto che una rotazione da u a v può essere ottenuta ruotando theta (l'angolo tra i vettori) attorno al vettore perpendicolare, sembra che possiamo costruire direttamente un quaternione che rappresenta una tale rotazione dai risultati dei prodotti punto e croce ; tuttavia, così com'è, theta = angle / 2 , il che significa che così facendo si otterrebbe il doppio della rotazione desiderata.

Una soluzione consiste nel calcolare un vettore a metà strada tra u e v , e utilizzare il punto e prodotto vettoriale di u e la metà strada vettoriale per costruire un quaternione rappresenta una rotazione di due volte l'angolo tra u e la metà strada vettore, che ci porta fino a v !

C'è un caso speciale, dove u == -v e un unico vettore a metà strada diventa impossibile da calcolare. Questo è previsto, date le infinite rotazioni dell '"arco più corto" che possono portarci da u a v , e dobbiamo semplicemente ruotare di 180 gradi attorno a qualsiasi vettore ortogonale a u (o v ) come soluzione del nostro caso speciale. Questo viene fatto prendendo il prodotto incrociato normalizzato di u con qualsiasi altro vettore non parallelo a u .

Segue lo pseudo codice (ovviamente, in realtà il caso speciale dovrebbe tenere conto delle imprecisioni in virgola mobile, probabilmente controllando i prodotti punto rispetto a una soglia piuttosto che a un valore assoluto).

Si noti inoltre che non esiste un caso speciale quando u == v (viene prodotto il quaternione di identità - controlla e guarda tu stesso).

// 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 orthogonalfunzione restituisce qualsiasi vettore ortogonale al vettore dato. Questa implementazione utilizza il prodotto incrociato con il vettore di base più ortogonale.

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

Soluzione di quaternione a metà strada

Questa è in realtà la soluzione presentata nella risposta accettata, e sembra essere leggermente più veloce della soluzione vettoriale a metà strada (~ 20% più veloce dalle mie misurazioni, anche se non credermi sulla parola). Lo aggiungo qui nel caso in cui altri come me siano interessati a una spiegazione.

In sostanza, invece di calcolare un quaternione usando un vettore a metà strada, puoi calcolare il quaternione che si traduce in due volte la rotazione richiesta (come dettagliato nell'altra soluzione) e trovare il quaternione a metà strada tra quello e zero gradi.

Come ho spiegato prima, il quaternione per il doppio della rotazione richiesta è:

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

E il quaternione per la rotazione zero è:

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

Calcolare il quaternione a metà strada è semplicemente una questione di sommare i quaternioni e normalizzare il risultato, proprio come con i vettori. Tuttavia, come anche nel caso dei vettori, i quaternioni devono avere la stessa grandezza, altrimenti il ​​risultato sarà sbilanciato verso il quaternione di grandezza maggiore.

Un quaternion costruito dal punto e prodotto vettoriale di due vettori avrà la stessa grandezza di quelli prodotti: length(u) * length(v). Invece di dividere tutti e quattro i componenti per questo fattore, possiamo invece aumentare il quaternione dell'identità. E se ti stavi chiedendo perché la risposta accettata apparentemente complica le cose usando sqrt(length(u) ^ 2 * length(v) ^ 2), è perché la lunghezza al quadrato di un vettore è più veloce da calcolare rispetto alla lunghezza, quindi possiamo salvare un sqrtcalcolo. Il risultato è:

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

E poi normalizza il risultato. Segue lo pseudo codice:

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

12
+1: fantastico! Questo ha funzionato come un fascino. Dovrebbe essere la risposta accettata.
Rekin

1
La sintassi dei quaternioni è attivata in alcuni esempi (Quaternion (xyz, w) e Quaternion (w, xyz)). Sembra anche che nell'ultimo blocco di codice radianti e gradi siano mescolati per esprimere angoli (180 vs. k_cos_theta + k).
Guillermo Blasco

1
Quaternion (float, Vector3) è una costruzione dal vettore scalare, mentre Quaternion (Vector3, float) è una costruzione dall'angolo dell'asse. Forse potenzialmente fonte di confusione, ma penso che sia corretto. Correggimi se pensi ancora che sia sbagliato!
Joseph Thomson

Ha funzionato! Grazie! Tuttavia, ho trovato un altro collegamento simile e ben spiegato per eseguire l'operazione sopra. Ho pensato di condividere per la cronaca;)
peccatore

1
@JosephThomson La soluzione a metà strada del quaternione sembra provenire da qui .
legends2k

6

Il problema come detto non è ben definito: non c'è una rotazione unica per una data coppia di vettori. Considera il caso, ad esempio, dove u = <1, 0, 0> e v = <0, 1, 0> . Una rotazione da u a v sarebbe una rotazione pi / 2 attorno all'asse z. Un'altra rotazione da u a v sarebbe una rotazione pi intorno al vettore <1, 1, 0> .


1
Non c'è infatti un numero infinito di possibili risposte? Perché dopo aver allineato il vettore "da" con il vettore "a" puoi ancora ruotare liberamente il risultato attorno al suo asse? Sapete quali informazioni extra possono essere tipicamente utilizzate per limitare questa scelta e rendere il problema ben definito?
Doug McClean

5

Perché non rappresentare il vettore usando quaternioni puri? Forse è meglio se prima li normalizzi.
q 1 = (0 u x u y u z ) '
q 2 = (0 v x v y v z )'
q 1 q rot = q 2
Pre-moltiplicare con q 1 -1
q rot = q 1 -1 q 2
dove q 1 -1 = q 1 conj / q norma
Questo può essere pensato come "divisione di sinistra". La divisione giusta, che non è ciò che vuoi, è:
q rot, right = q 2 -1 q 1


2
Mi sono perso, la rotazione da q1 a q2 non è calcolata come q_2 = q_rot q_1 q_rot ^ -1?
yota

4

Non sono molto bravo con Quaternion. Tuttavia ho lottato per ore su questo e non sono riuscito a far funzionare la soluzione Polaris878. Ho provato a pre-normalizzare v1 e v2. Normalizzare q. Normalizzare q.xyz. Eppure ancora non lo capisco. Il risultato ancora non mi ha dato il risultato giusto.

Alla fine però ho trovato una soluzione che ha funzionato. Se aiuta qualcun altro, ecco il mio codice (python) funzionante:

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 caso speciale deve essere fatto se v1 e v2 sono paralleli come v1 == v2 o v1 == -v2 (con una certa tolleranza), dove credo che le soluzioni dovrebbero essere Quaternion (1, 0,0,0) (nessuna rotazione) o Quaternion (0, * v1) (rotazione di 180 gradi)


Ho un'implementazione funzionante, ma questa è più carina, quindi volevo davvero che funzionasse. Purtroppo ha fallito tutti i miei casi di prova. I miei test sembrano tutti qualcosa di simile quat = diffVectors(v1, v2); assert quat * v1 == v2.
sinisterchipmunk

È improbabile che funzioni affatto poiché angleottiene il suo valore da un prodotto dot.
sam hocevar

Dov'è la funzione Quaternion ()?
June Wang

3

Alcune delle risposte non sembrano considerare la possibilità che il prodotto incrociato possa essere 0. Il frammento di seguito utilizza la rappresentazione dell'asse angolare:

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

La toQuaternionpuò essere implementato come segue:

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

Se stai usando la libreria Eigen, puoi anche fare semplicemente:

Quaternion::FromTwoVectors(from, to)

toQuaternion(axis, ang)-> hai dimenticato di specificare cos'èang
Maksym Ganenko

Il 2 ° parametro è angleche fa parte della rappresentazione asse-angolo del quaternione, misurato in radianti.
Shital Shah,

Ti è stato chiesto di far ruotare il quaternione da un vettore all'altro. Non hai l'angolo, devi prima calcolarlo. La tua risposta dovrebbe contenere il calcolo dell'angolo. Saluti!
Maksym Ganenko

Questo è c ++? cos'è ux ()?
June Wang

Sì, questo è C ++. u è il tipo vettoriale della libreria Eigen (se ne stai usando una).
Shital Shah

2

Dal punto di vista dell'algoritmo, la soluzione più veloce guarda in pseudocodice

 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
 }

Assicurati di aver bisogno di quaternioni unitari (di solito è necessario per l'interpolazione).

NOTA: i quaternioni non unitari possono essere utilizzati con alcune operazioni più veloci di unit.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.