33
\$\begingroup\$

I am attempting to rotate a vector3 by a given quaternion.

I know that this is true

$$ v' = q \cdot v \cdot q^{-1} $$

I know that \$ q^{-1} \$ is the inverse which just \$ \frac {-q} {magnitude(q)} \$, but how do I map the multiplication of the vector to the quaternion to get back a vector?

I have found that you can treat \$v\$ as a matrix, and convert \$q\$ and \$q'\$ to matrices, and then convert \$v'\$ from a matrix to a vector, but this seems a little over the top just to get a vector. Is there a cleaner implementation that I could use?

\$\endgroup\$
0

6 Answers 6

55
\$\begingroup\$

As Nathan Reed and teodron exposed, the recipe for rotating a vector v by a unit-length quaternion q is:

  1. Create a pure quaternion p out of v. This simply means adding a fourth coordinate of 0:

$$p = (v_x, v_y, v_z, 0) \Leftrightarrow p = (v, 0)$$

  1. Pre-multiply it with q and post-multiply it with the conjugate q*:

$$p' = q \times p \times q*$$

  1. This will result in another pure quaternion which can be turned back to a vector:

$$v' = (p'_x, p'_y, p'_z)$$

This vector \$v'\$ is \$v\$ rotated by \$q\$.


This is working but far from optimal. Quaternion multiplications mean tons and tons of operations. I was curious about various implementations such as this one, and decided to find from where those came. Here are my findings.

We can also describe q as the combination of a 3-dimensional vector u and a scalar s:

$$q = (u_x, u_y, u_z, s) \Leftrightarrow q = (u, s)$$

By the rules of quaternion multiplication, and as the conjugate of a unit length quaternion is simply it's inverse, we get:

$$ \begin{align} p' &= qpq* \\ &= (u, s)(v,0)(-u, s) \\ &= (sv + u \times v, -u \cdot v)(-u, s) \\ &= ((-u \cdot v)(-u) + s(sv + u \times\ v) + (sv + u \times v) \times (-u), \dots) \\ &= ((u \cdot v) u + s^2v + s(u \times v) + sv \times (-u) + (u \times v) \times (-u), \dots) \end{align} $$

The scalar part (ellipses) results in zero, as detailed here. What's interesting is the vector part, AKA our rotated vector v'. It can be simplified using some basic vector identities:

$$ \begin{align} v' &= (u \cdot v)u + s^2v + s(u \times v) + s(u \times v) + u \times (u \times v) \\ &= (u \cdot v)u + s^2v + 2s(u \times v) + (u \cdot v)u - (u \cdot u)v \\ &= 2(u \cdot v)u + (s^2 - u \cdot u)v + 2s(u \times v) \end{align} $$

This is now much more optimal; two dot products, a cross product and a few extras: around half the operations. Which would give something like that in source code (assuming some generic vector math library):

void rotate_vector_by_quaternion(const Vector3& v, const Quaternion& q, Vector3& vprime)
{
    // Extract the vector part of the quaternion
    Vector3 u(q.x, q.y, q.z);

    // Extract the scalar part of the quaternion
    float s = q.w;

    // Do the math
    vprime = 2.0f * dot(u, v) * u
           + (s*s - dot(u, u)) * v
           + 2.0f * s * cross(u, v);
}
\$\endgroup\$
8
  • \$\begingroup\$ Hats off to a better written response. And considering that most performance freaks tend to use intrinsics to perform vector operations, you do get quite a speed-up (even for plain quaternion multiplication, especially on intel architectures). \$\endgroup\$
    – teodron
    Commented Mar 6, 2013 at 17:25
  • \$\begingroup\$ The final result looks similar to Rodrigues' rotation formula - it has the same basis vectors, anyway; I'd have to dig into some trig identities to see if the coefficients match. \$\endgroup\$ Commented Jul 26, 2013 at 18:40
  • 3
    \$\begingroup\$ I was checking GLM's implementation of this and it seems to be implemented a bit differently, namely like follows: vprime = v + ((cross(u, v) * s) + cross(u, cross(u, v)) * 2.0f Is this a similar optimization? It looks somewhat similar, but isn't the same - it uses cross products only, no dot products. The original source code can be found in the official GLM repository's type_quat.inl file in the operator* which takes a quaternion and a vector (vec<3, T, Q> operator*(qua<T, Q> const& q, vec<3, T, Q> const& v)) \$\endgroup\$
    – j00hi
    Commented Apr 18, 2019 at 9:52
  • 2
    \$\begingroup\$ I was also wondering about the following: usually rotations are described by a unit quaternion, right? In this case, should we well have (s*s - dot(u, u)) = 2 s*s -1 ? I suppose this would further simplify the calculation? \$\endgroup\$
    – Zorglub29
    Commented Apr 24, 2021 at 15:49
  • 2
    \$\begingroup\$ I agree with @Zorglub29 - the optimization is valid. To verify componentwise, we haves² - dot(u, u) = w² - x² - y² - z². Then, using the unit quaternion property, add 1 = w² + x² + y² + z² and cancel, yielding 2 w², and subtract 1 again. \$\endgroup\$
    – japreiss
    Commented Jul 20, 2021 at 0:29
7
\$\begingroup\$

First of all, q^(-1) is not -q/magnitude(q); it's q*/(magnitude(q))^2 (q* is the conjugate; that negates all the components except the real one). Of course, you can leave off the division by the magnitude if all your quaternions are normalized already, which they typically would be in a rotation system.

As for the multiplication with a vector, you just extend the vector to a quaternion by setting a quat's real component to zero and its ijk components to the vector's xyz. Then you do the quaternion multiplications to get v', and then extract the ijk components again. (The real part of v' should always come out zero, plus or minus some floating-point error.)

\$\endgroup\$
5
\$\begingroup\$

First observation: The inverse of q is not -q/magnitude(q), that is completely wrong. Rotations with quaternions imply that these 4D complex number equivalents have unitary norm, hence lie on the S3 unit sphere in that 4D space. The fact that a quat is unitary means that its norm is norm(q)^2=q*conjugate(q)=1 and that means that the quat's inverse is its conjugate.

If a unit quaternion is written as q=(w,x,y,z) = (cos(t),sin(t)v), then its conjugate is conjugate(q)=(w,-x,-y,-z)=(cos(t),-sin(t)v), where t is half of the rotation angle and v is the rotation axis (as a unit vector, of course).

When that Hamilton dude decided to play around with complex number equivalents in higher dimensions, he also stumbled upon some nice properties. For example, if you employ a completely pure quaternion q=(0,x,y,z) (no scalar part w !), you can consider that crap as being a vector (it's actually a quat on what people might call the equator of the S3 sphere, which is an S2 sphere!! - mind bending stuff if we consider how technically impaired the people in the 19th century seem to us eyePhone cowboys nowadays). So Hamilton took that vector in its quat form: v=(0,x,y,z) and did a series of experiments considering the geometric properties of quats.. Long story short:

INPUT: _v=(x,y,z)_ a random 3D vector to rotate about an __u__ unit axis by an angle of _theta_

OUTPUT: q*(0,_v_)*conjugate(q)

where

 q = (cos(theta/2), sin(theta/2)*u)
 conjugate(q) = inverse(q) = (cos(theta/2), -sin(theta/2)*u)
 norm(q)=magnitude(q)=|q|=1

Observation: the q*(0,v)*conj(q) has to be another quat of the form (0,v'). I won't go through all that apparently complicated explanation of why this happens, but if you rotate a pure imaginary quaternion (or a vector in our case!) through this method,you must get a similar kind of object: pure imaginary quat.. and you take its imaginary part as your result. There you have it, the wonderful world of rotations with quaternions in a nut(ty)shell.

NOTE: to whomever jumps in with that overused phrase: quats are good because they avoid 'em gimbal lock.. should unlock their imagination first!! Quats are a mere "elegant" mathematical apparatus and can be avoided altogether by using other approaches, the one I find completely geometrically equivalent being the axis angle approach.

CODE: the C++ library I fancy is rather simplistic, but has all the matrix, vector and quat operations a 3D graphics experimentalist should need without having to waste more than 15 minutes to learn it.. You can test the things I wrote here using that in 15 minutes if you're not a C++ novice. Good luck!

\$\endgroup\$
4
  • \$\begingroup\$ +1 for your note. I bet most folks couldn't achieve real gimbal lock if they tried.It has become a catch all phrase for any unexpected behavior when performing rotations. \$\endgroup\$
    – Steve H
    Commented May 1, 2012 at 20:51
  • \$\begingroup\$ Most people can't build a proper gimbal mechanism and think that if they chain together 3 rotations matrices, they automatically end up with the "Euler angles" representation.. The gimbal thingie is just one of the simplest robot-arm-type of rotational joints that can experience redundancy when trying to perform inverse kinematics (it has more degrees of freedom than it actually needs to produce the desired orientation). Oh well, that's another topic, but I thought it's nice to stay away from the hype this "legendary" issue has generated among CG programmers.. \$\endgroup\$
    – teodron
    Commented May 1, 2012 at 21:11
  • \$\begingroup\$ Nitpickery: while axis-angle is equivalent in that both representations can represent all rotations in SO(3) uniquely (okay, modulo the usual double-cover) and of course there's a nearly-trivial transformation back and forth between them, quaternions do have the advantage of being much easier to compose than all of the other non-matrix representations. \$\endgroup\$ Commented May 3, 2012 at 6:09
  • \$\begingroup\$ They have the advantage of being easier to compose due to their nice behaviour in any object oriented programming language, especially when using operator overloading. I'm not sure, but perhaps even their spherical interpolation properties preserve for axis-angle (except for SQUAD maybe?!). \$\endgroup\$
    – teodron
    Commented May 3, 2012 at 8:34
2
\$\begingroup\$

Here is an alternative way to transform a vector by a quaternion. It is the way MS does it in the xna framework.

public static Vector3 Transform(Vector3 value, Quaternion rotation)
{
    Vector3 vector;
    float num12 = rotation.X + rotation.X;
    float num2 = rotation.Y + rotation.Y;
    float num = rotation.Z + rotation.Z;
    float num11 = rotation.W * num12;
    float num10 = rotation.W * num2;
    float num9 = rotation.W * num;
    float num8 = rotation.X * num12;
    float num7 = rotation.X * num2;
    float num6 = rotation.X * num;
    float num5 = rotation.Y * num2;
    float num4 = rotation.Y * num;
    float num3 = rotation.Z * num;
    float num15 = ((value.X * ((1f - num5) - num3)) + (value.Y * (num7 - num9))) + (value.Z * (num6 + num10));
    float num14 = ((value.X * (num7 + num9)) + (value.Y * ((1f - num8) - num3))) + (value.Z * (num4 - num11));
    float num13 = ((value.X * (num6 - num10)) + (value.Y * (num4 + num11))) + (value.Z * ((1f - num8) - num5));
    vector.X = num15;
    vector.Y = num14;
    vector.Z = num13;
    return vector;
}
\$\endgroup\$
0
\$\begingroup\$

In C using Hamilton products. The rotate function takes a 3d vector for the point you want to rotate and the axis of rotation, and then the output is another 3d vector that you pass in to be modified.

hamilton_product function just does the operations defined by this wikipedia section: https://en.wikipedia.org/wiki/Quaternion#Hamilton_product

void hamilton_product(const float* q, const float* r, float *output){
  output[0] = (q[0] * r[0]) - (q[1] * r[1]) - (q[2] * r[2]) - (q[3] * r[3]);
  output[1] = (q[0] * r[1]) + (q[1] * r[0]) + (q[2] * r[3]) - (q[3] * r[2]);
  output[2] = (q[0] * r[2]) - (q[1] * r[3]) + (q[2] * r[0]) + (q[3] * r[1]);
  output[3] = (q[0] * r[3]) + (q[1] * r[2]) - (q[2] * r[1]) + (q[3] * r[0]);
}

void make_quaternion(const float* axis, const float theta, float *output){
  float sin_theta = sin(theta);

  output[0] = cos(theta);
  output[1] = sin_theta * axis[0];
  output[2] = sin_theta * axis[1];
  output[3] = sin_theta * axis[2];
}

void rotate(const float* point, const float* axis, const float theta, float *output){
  static float q[4];
  static float q_conj[4];
  static float o4d[4];
  static float p[4];
  static float inter[4];
  make_quaternion(axis, theta/2, q);

  memcpy(q_conj, q, sizeof(q_conj));
  q_conj[1] = -q[1];
  q_conj[2] = -q[2];
  q_conj[3] = -q[3];
  
  p[0] = 0.0F;
  memcpy(&p[1], point, 3*sizeof(float));
  
  hamilton_product(q, p, o4d);

  memcpy(inter, o4d, sizeof(o4d));
  
  hamilton_product(inter, q_conj, o4d);

  memcpy(output, &o4d[1], 3*sizeof(float));
}
```
\$\endgroup\$
-1
\$\begingroup\$

I tried to work this out by hand, and came up with the following equation/method:

// inside quaterion class
// quaternion defined as (r, i, j, k)
Vector3 rotateVector(const Vector3 & _V)const{
    Vector3 vec();   // any constructor will do
    vec.x = 2*(r*_V.z*j + i*_V.z*k - r*_V.y*k + i*_V.y*j) + _V.x*(r*r + i*i - j*j - k*k);
    vec.y = 2*(r*_V.x*k + i*_V.x*j - r*_V.z*i + j*_V.z*k) + _V.y*(r*r - i*i + j*j - k*k);
    vec.z = 2*(r*_V.y*i - r*_V.x*j + i*_V.x*k + j*_V.y*k) + _V.z*(r*r - i*i - j*j + k*k);
    return vec;
}

I would appreciate if someone would look over mt deriviation I used http://pastebin.com/8QHQqGbv I would suggest to copy to a text editor that supports side scrolling

in my notation I used q^(-1) to mean conjugate, and not inverse, and different identifiers, but I hope that it is follow-able. I think that the majority is right especially where on proving the real portion of the vector would disappear.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .