Ho due vettori ue v. C'è un modo per trovare un quaternione che rappresenta la rotazione da u a v?
Ho due vettori ue v. C'è un modo per trovare un quaternione che rappresenta la rotazione da u a v?
Risposte:
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.
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.
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 orthogonal
funzione 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);
}
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 sqrt
calcolo. 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)));
}
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> .
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
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)
quat = diffVectors(v1, v2); assert quat * v1 == v2
.
angle
ottiene il suo valore da un prodotto dot.
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 toQuaternion
può 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
angle
che fa parte della rappresentazione asse-angolo del quaternione, misurato in radianti.
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.
crossproduct
non sarà valido in questi casi, quindi devi prima controllaredot(v1, v2) > 0.999999
edot(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.