Here's another way to think of a rotation in 3D space: rotating a vector about another vector (or direction). Quaternions have an algebra that allows this view of rotation.
Quaternions are seen as a generalization of complex numbers. A quaternion is a 4-tuple where .
The product of two quaternions and is:
Now if , then which is equal to the square of the norm of the vector .
One can create two quaternions from a 3D unit vector and an angle like so:
Note that
If is a unit vector, then is also a unit vector for any .
Thus, is the inverse of and we can write it as .
Let's say we want to rotate a vector around an axis represented by a unit vector . Let's denote the output vector as . Then:
Here we took the liberty to represent a quaternion as a vector . One can verify the above equation by multiplying the quaternions on the right hand side.
Let's see how it looks like for 2D rotations.
Since quaternions describe rotations in 3D, once can think of a rotation of by in a 2D plane as rotation of about the z-axis by the same angle.
It is essentially this operation:
The reverse mapping simply drops the first (zero) component of :
Unlike the 2D case, in general all four numbers in the product are non-zero. So it is not possible to visualize it.
However the product always has the first value equal to 0, which allows one to interpret it as a 3D vector:
One can visualize 3D rotations by rotating a cube.
deg, currently set to . deg to to see how the cube rotates.A quaternion can be represented as a matrix:
where and .
class Quaternion:
def __init__(self, w:float, x:float, y:float, z:float):
self.w = w
self.x = x
self.y = y
self.z = z
def norm(self):
return np.sqrt(self.w**2 + self.x**2 + self.y**2 + self.z**2)
def unit(self):
n = self.norm()
return Quaternion(self.w/n, self.x/n, self.y/n, self.z/n)
def conjugate(self):
return Quaternion(self.w, -self.x, -self.y, -self.z)
def __mul__(self, other):
w = self.w*other.w - self.x*other.x - self.y*other.y - self.z*other.z
x = self.w*other.x + self.x*other.w + self.y*other.z - self.z*other.y
y = self.w*other.y - self.x*other.z + self.y*other.w + self.z*other.x
z = self.w*other.z + self.x*other.y - self.y*other.x + self.z*other.w
return Quaternion(w, x, y, z)
def to_matrix(self):
u = self.unit()
w,x,y,z = u.w, u.x, u.y, u.z
xx, yy, zz = x*x, y*y, z*z
xy, xz, yz = x*y, x*z, y*z
wx, wy, wz = w*x, w*y, w*z
m = [[1 - 2*(yy + zz), 2*(xy - wz), 2*(xz + wy)],
[2*(xy + wz), 1 - 2*(xx + zz), 2*(yz - wx)],
[2*(xz - wy), 2*(yz + wx), 1 - 2*(xx + yy)]]
return np.array(m)
Here is how one would use it to rotate a vector:
def rotate_vector(
axis_x:float, axis_y:float, axis_z:float,
angle:float,
vector_x:float, vector_y:float, vector_z:float
):
cos_angle, sin_angle = np.cos(angle/2), np.sin(angle/2)
# make the axis a unit vector
axis_norm = np.sqrt(axis_x**2 + axis_y**2 + axis_z**2)
axis_x, axis_y, axis_z = axis_x/axis_norm, axis_y/axis_norm, axis_z/axis_norm
q = Quaternion(cos_angle, sin_angle*axis_x, sin_angle*axis_y, sin_angle*axis_z)
v = Quaternion(0, vector_x, vector_y, vector_z)
y = q * v * q.conjugate()
return y.x, y.y, y.z