- Hands-On C++ Game Animation Programming
- Gabor Szauer
- 790字
- 2021-06-30 14:46:02
Multiplying quaternions
Two quaternions can be concatenated by multiplying them together. Like with matrices, the operation is carried out from right to left; the right quaternion's rotation is applied first and then the left quaternion's.
Assume you have two quaternions, q and p. They are subscripted with 0, 1, 2, and 3, which correspond to the X, Y, Z, and W components, respectively. These quaternions can be expressed in ijk notation, as shown:
To multiply these two quaternions together, distribute the components of p to the components of q. Distributing the real component is simple. Distributing p3 to q would look like this:
Distributing the imaginary components looks very similar. The real and imaginary parts are combined separately; the order of imaginary components matters. For example, distributing poi to q would look like this:
Fully distributing p to q looks like this:
Start simplifying for the case when imaginary numbers are squared. The square root of an imaginary number is -1. If you raise -1 to the power of -1, the result is also -1. This means that any instance of i2, j2, or k2 can be replaced by -1, like so:
What about the rest of the imaginary numbers? When talking about quaternions,
ijk= -1, the squared value of each of these components is also -1, which means that
i2= j2= k2=ijk. This property of quaternions can be used to simplify the rest of the equation.
Take jk, for example. Start with ijk= -1 and try to isolate jk to one side of the equation.
To do this, multiply both sides by i, leaving you with i(ijk)= -i. Distribute i, which will leave you with i2 jk= -i. You already know that the value of i2 is -1. Substitute it to get
-jk= -i. Multiply both sides by -1 and you have found the value of jk— jk=i.
The values for ki and ij can be found in a similar way; they are ki=j and k=ij. You can now substitute any instances of ki with j, ij with k, and jk with i. Substituting these values leaves you with the following:
The remaining imaginary numbers are ik, ji, and kj. Like the cross product, the order matters: ik= -ki. From this, you can assume that ik= -j, ji= -k, and kj= -1. Substituting these values leaves you with the following:
Numbers with different imaginary components cannot be added together. Re-arrange the preceding formula so that like imaginary components are next to each other. This results in the final equation for quaternion multiplication:
To implement this formula in code, change from this subscripted ijk notation back to vector notation with X, Y, Z, and W subscripts. Implement the quaternion multiplication function in quat.cpp and don't forget to add the function declaration to quat.h:
quat operator*(const quat& Q1, const quat& Q2) {
return quat(
Q2.x*Q1.w + Q2.y*Q1.z - Q2.z*Q1.y + Q2.w*Q1.x,
-Q2.x*Q1.z + Q2.y*Q1.w + Q2.z*Q1.x + Q2.w*Q1.y,
Q2.x*Q1.y - Q2.y*Q1.x + Q2.z*Q1.w + Q2.w*Q1.z,
-Q2.x*Q1.x - Q2.y*Q1.y - Q2.z*Q1.z + Q2.w*Q1.w
);
}
When looking at the preceding code, notice that the real part of the quaternion has one positive component, but the vector part has one negative component. Re-arrange the quaternion so that the negative numbers are always last. Write it down using vector notation:
qpx= px qw+ pw qx+ py qz- pz qy
qpy= py qw+ pw qy+ pz qx- px qz
qpz= pz qw+ pw qz+ px qy- py qx
qpw= pw qw- px qx- py qy- pz qz
There are two interesting parts in the preceding equation. If you look closely at the last two columns of the first three rows, the columns with the subtraction are the cross product. The first two columns are just scaling the vector parts of each quaternion by the scalar parts of the others.
If you look at the last row, the dot product is in there with the negative of the dot product. The last row is basically multiplying the real parts of both quaternions, then subtracting the dot product of their vector parts. This means that an alternate multiplication implementation could look like this:
quat operator*(const quat& Q1, const quat& Q2) {
quat result;
result.scalar = Q2.scalar * Q1.scalar -
dot(Q2.vector, Q1.vector);
result.vector = (Q1.vector * Q2.scalar) +
(Q2.vector * Q1.scalar)+cross(Q2.vector, Q1.vector);
return result;
}
The original implementation is a bit more performant since it doesn't need to invoke other functions. The sample code for this book will use the first implementation.
Next, you will learn how to transform vectors by quaternions.