Let \(\mathbf{u},\mathbf{v}\in\mathbb{R}^3\setminus\{\mathbf{0}\}\) be two nonzero vectors. We seek a unit quaternion \(\hat{\mathbf{q}}\) whose rotation maps the direction of \(\mathbf{u}\) to the direction of \(\mathbf{v}\). Unless the directions are opposite, there is a unique shortest rotation: its axis is perpendicular to both vectors and its angle is the angle between them.
Write a quaternion as \(\mathbf{q}=(w,\mathbf{x})\), with scalar part \(w\) and vector part \(\mathbf{x}\), matching the notation used for quaternions. The result can be obtained without evaluating an inverse trigonometric function.
The Geometric Construction
Normalize the input directions conceptually:
\[\hat{\mathbf{u}}=\frac{\mathbf{u}}{\|\mathbf{u}\|},\qquad \hat{\mathbf{v}}=\frac{\mathbf{v}}{\|\mathbf{v}\|}.\]
If \(0<\theta<\pi\), the oriented rotation axis is the normalized cross product
\[\hat{\mathbf{n}}= \frac{\hat{\mathbf{u}}\times\hat{\mathbf{v}}} {\|\hat{\mathbf{u}}\times\hat{\mathbf{v}}\|}.\]
The axis-angle representation of the shortest rotation is therefore
\[\hat{\mathbf{q}}= \left(\cos\frac{\theta}{2},\hat{\mathbf{n}}\sin\frac{\theta}{2}\right).\]
From the dot product and cross product,
\[\hat{\mathbf{u}}\cdot\hat{\mathbf{v}}=\cos\theta, \qquad \|\hat{\mathbf{u}}\times\hat{\mathbf{v}}\|=\sin\theta.\]
Using \(\sin\theta=2\sin(\theta/2)\cos(\theta/2)\), the vector part becomes
\[\begin{array}{rl} \hat{\mathbf{n}}\sin\dfrac{\theta}{2} &=\dfrac{\hat{\mathbf{u}}\times\hat{\mathbf{v}}}{\sin\theta} \sin\dfrac{\theta}{2}\\ &=\dfrac{\hat{\mathbf{u}}\times\hat{\mathbf{v}}} {2\cos(\theta/2)}. \end{array}\]
Both quaternion parts may be multiplied by the same positive factor before the final normalization. Multiplying by \(2\cos(\theta/2)\) gives the unnormalized quaternion
\[\mathbf{q}=\left(1+\cos\theta, \hat{\mathbf{u}}\times\hat{\mathbf{v}}\right).\]
Formula for Arbitrary Nonzero Vectors
Set
\[d=\mathbf{u}\cdot\mathbf{v},\qquad \mathbf{c}=\mathbf{u}\times\mathbf{v},\qquad m=\|\mathbf{u}\|\|\mathbf{v}\|.\]
Multiplying the unit-vector formula by \(m\) removes both input normalizations:
\[\boxed{\mathbf{q}=\left(m+d,\mathbf{c}\right)}.\]
Only the quaternion itself must be normalized. Lagrange's identity gives
\[\|\mathbf{c}\|^2 =\|\mathbf{u}\|^2\|\mathbf{v}\|^2-d^2 =m^2-d^2.\]
Consequently,
\[\begin{array}{rl} \|\mathbf{q}\|^2 &=(m+d)^2+\|\mathbf{c}\|^2\\ &=(m+d)^2+m^2-d^2\\ &=2m(m+d). \end{array}\]
For every non-antiparallel pair, \(m+d>0\), so the desired unit quaternion is
\[\boxed{ \hat{\mathbf{q}}= \frac{\left(m+d,\mathbf{u}\times\mathbf{v}\right)} {\sqrt{2m(m+d)}} }.\]
This is the shortest rotation from the direction of \(\mathbf{u}\) to the direction of \(\mathbf{v}\). Reversing the cross product reverses the rotation: use \(\mathbf{v}\times\mathbf{u}\) only when the desired mapping is from \(\mathbf{v}\) to \(\mathbf{u}\).
Unit-Vector Case
When both inputs are already unit vectors, \(m=1\), and the formula reduces to
\[\boxed{ \hat{\mathbf{q}}= \frac{\left(1+\hat{\mathbf{u}}\cdot\hat{\mathbf{v}}, \hat{\mathbf{u}}\times\hat{\mathbf{v}}\right)} {\sqrt{2\left(1+\hat{\mathbf{u}}\cdot\hat{\mathbf{v}}\right)}} }.\]
The numerator alone is often quoted as the construction, with quaternion normalization understood as the final operation.
Degenerate and Boundary Cases
Zero Vectors
A zero vector has no direction. Therefore no rotation from or to \(\mathbf{0}\) is defined, and an implementation must reject either zero-length input.
Parallel Vectors
For vectors pointing in the same direction, \(d=m\) and \(\mathbf{c}=\mathbf{0}\). Then \(\mathbf{q}=(2m,\mathbf{0})\), which normalizes to the identity quaternion \((1,\mathbf{0})\). No special branch is required.
Antiparallel Vectors
For opposite directions, \(d=-m\) and \(\mathbf{c}=\mathbf{0}\), so the general construction becomes the zero quaternion and cannot be normalized. Geometrically, the rotation angle is \(\pi\), but its axis is not unique: every unit vector \(\hat{\mathbf{n}}\) perpendicular to \(\mathbf{u}\) gives a valid result,
\[\hat{\mathbf{q}}= \left(\cos\frac{\pi}{2},\hat{\mathbf{n}}\sin\frac{\pi}{2}\right) =(0,\hat{\mathbf{n}}).\]
A robust implementation chooses the coordinate axis least aligned with \(\mathbf{u}\), crosses it with \(\mathbf{u}\), and normalizes the result. Choosing the least-aligned axis keeps that auxiliary cross product away from zero.
Numerical Stability Near 180 Degrees
When the vectors are almost opposite, \(m+d\) subtracts nearly equal numbers. Lagrange's identity supplies an equivalent expression that avoids this cancellation:
\[(m+d)(m-d)=m^2-d^2=\|\mathbf{c}\|^2,\]
and hence, for \(d<0\) outside the exactly antiparallel case,
\[m+d=\frac{\|\mathbf{c}\|^2}{m-d}.\]
This form computes the small scalar part from quantities that do not cancel. The quaternion still needs one final normalization, but no trigonometric function is required.
JavaScript Implementation
The implementation below returns \([w,x,y,z]\), uses the stable scalar formula near opposite directions, rejects zero vectors, and resolves the antiparallel ambiguity deterministically:
function quaternionFromVectors(u, v, epsilon = 1e-12) {
function dot(a, b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
function cross(a, b) {
return [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]
];
}
const uu = dot(u, u);
const vv = dot(v, v);
if (uu === 0 || vv === 0) {
throw new RangeError("A zero vector has no direction");
}
const d = dot(u, v);
const c = cross(u, v);
const c2 = dot(c, c);
const m = Math.sqrt(uu * vv);
if (d < 0 && c2 <= epsilon * epsilon * uu * vv) {
const absolute = u.map(Math.abs);
const basis = absolute[0] <= absolute[1] && absolute[0] <= absolute[2]
? [1, 0, 0]
: absolute[1] <= absolute[2]
? [0, 1, 0]
: [0, 0, 1];
const axis = cross(u, basis);
const inverseAxisLength = 1 / Math.sqrt(dot(axis, axis));
return [
0,
axis[0] * inverseAxisLength,
axis[1] * inverseAxisLength,
axis[2] * inverseAxisLength
];
}
const scalar = d < 0 ? c2 / (m - d) : m + d;
const inverseLength = 1 / Math.sqrt(scalar * scalar + c2);
return [
scalar * inverseLength,
c[0] * inverseLength,
c[1] * inverseLength,
c[2] * inverseLength
];
} For example, rotating \((1,0,0)\) toward \((0,1,0)\) produces \((\sqrt{2}/2,0,0,\sqrt{2}/2)\), a 90-degree rotation around the positive z-axis. Rotating toward \((-1,0,0)\) enters the antiparallel branch and returns a 180-degree rotation around a deterministic axis perpendicular to the x-axis.
The same construction is available in the Quaternion.js library for applications that already use its quaternion operations.
Verification
Three checks characterize the result:
- \(\|\hat{\mathbf{q}}\|=1\).
- Applying \(\hat{\mathbf{q}}\) to \(\hat{\mathbf{u}}\) yields \(\hat{\mathbf{v}}\).
- For non-antiparallel vectors, the scalar part is nonnegative, so the represented angle lies in \([0,\pi]\) and is the shortest possible rotation.
The quaternion \(-\hat{\mathbf{q}}\) represents exactly the same 3D rotation. A sign convention such as a nonnegative scalar part merely chooses one representative of this double cover; it does not change the rotation.