Solución:
Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);
No olvide normalizar q.
Richard tiene razón en que no hay una rotación única, pero lo anterior debería dar el “arco más corto”, que es probablemente lo que necesita.
Solución vectorial a mitad de camino
Se me ocurrió la solución que creo que Imbrondir estaba tratando de presentar (aunque con un error menor, que probablemente fue la razón por la que sinisterchipmunk tuvo problemas para verificarlo).
Dado que podemos construir un cuaternión que represente una rotación alrededor de un eje así:
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
Y que el punto y el producto cruzado de dos vectores normalizados son:
dot == cos(theta)
cross.x == sin(theta) * perpendicular.x
cross.y == sin(theta) * perpendicular.y
cross.z == sin(theta) * perpendicular.z
Viendo como una rotacion de tu para v se puede lograr girando theta (el ángulo entre los vectores) alrededor del vector perpendicular, parece que podemos construir directamente un cuaternión que represente dicha rotación a partir de los resultados de los productos punto y cruzado; sin embargo, tal como está, theta = ángulo / 2, lo que significa que hacerlo daría como resultado el doble de la rotación deseada.
Una solución es calcular un vector a medio camino entre tu y vy use el punto y el producto cruzado de tu y el Medio camino vector para construir un cuaternión que representa una rotación de dos veces el ángulo entre tu y el Medio camino vector, que nos lleva hasta v!
Hay un caso especial, donde u == -v y un vector único a mitad de camino se vuelve imposible de calcular. Esto es de esperar, dadas las infinitas rotaciones de “arco más corto” que pueden llevarnos de tu para v, y simplemente debemos rotar 180 grados alrededor de cualquier vector ortogonal a tu (o v) como nuestra solución para casos especiales. Esto se hace tomando el producto cruzado normalizado de tu con cualquier otro vector no Paralelo a tu.
A continuación, se muestra un pseudocódigo (obviamente, en realidad, el caso especial tendría que tener en cuenta las inexactitudes del punto flotante, probablemente al comparar los productos punto con algún umbral en lugar de un valor absoluto).
También tenga en cuenta que hay no caso especial cuando u == v (se produce el cuaternión de identidad – compruébelo y compruébelo usted mismo).
// 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));
}
los orthogonal
La función devuelve cualquier vector ortogonal al vector dado. Esta implementación utiliza el producto cruzado con el vector de base más ortogonal.
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);
}
Solución de cuaternión a medio camino
Esta es en realidad la solución presentada en la respuesta aceptada, y parece ser marginalmente más rápida que la solución de vector de mitad de camino (~ 20% más rápido según mis mediciones, aunque no confíe en mi palabra). Lo agrego aquí en caso de que otros como yo estén interesados en una explicación.
Esencialmente, en lugar de calcular un cuaternión usando un vector de mitad de camino, puede calcular el cuaternión que da como resultado el doble de la rotación requerida (como se detalla en la otra solución) y encontrar el cuaternión a mitad de camino entre eso y cero grados.
Como expliqué antes, el cuaternión para duplicar la rotación requerida es:
q.w == dot(u, v)
q.xyz == cross(u, v)
Y el cuaternión para la rotación cero es:
q.w == 1
q.xyz == (0, 0, 0)
Calcular el cuaternión a mitad de camino es simplemente una cuestión de sumar los cuaterniones y normalizar el resultado, al igual que con los vectores. Sin embargo, como también es el caso de los vectores, los cuaterniones deben tener la misma magnitud, de lo contrario el resultado estará sesgado hacia el cuaternión con la magnitud mayor.
Un cuaternión construido a partir del producto punto y cruzado de dos vectores tendrá la misma magnitud que esos productos: length(u) * length(v)
. En lugar de dividir los cuatro componentes por este factor, podemos escalar el cuaternión de identidad. Y si se pregunta por qué la respuesta aceptada aparentemente complica las cosas al usar sqrt(length(u) ^ 2 * length(v) ^ 2)
, es porque la longitud al cuadrado de un vector es más rápida de calcular que la longitud, por lo que podemos guardar una sqrt
cálculo. El resultado es:
q.w = dot(u, v) + sqrt(length_2(u) * length_2(v))
q.xyz = cross(u, v)
Y luego normaliza el resultado. A continuación, se muestra el pseudocódigo:
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)));
}
El problema, como se dijo, no está bien definido: no existe una rotación única para un par de vectores dado. Considere el caso, por ejemplo, donde u = <1, 0, 0> y v = <0, 1, 0>. Una rotación de u a v sería una pi / 2 rotación alrededor del eje z. Otra rotación de u a v sería una Pi rotación alrededor del vector <1, 1, 0>.