Can a smart person please explain this gem:
Code:
Vector3 dir = Quaternion.AngleAxis(90, cam.transform.right) * hit.normal;
I know this rotates the normal vector 90° around the right axis and it does what it should but I want to know what exactly is going on here.
Quaternion.AngleAxis(90, cam.transform.right) makes me go
Here we go...
About the Connection between Rotations and the Quaternions
Before we dive into the quaternions and their connection with rotations, I
want to show that there exists something very similar to the quaternions in
representing rotations, which is way easier to understand and will help to
bring the connection across.
The Principle Axis and Angle of Rotation
Theorem: Any coordinate frame can be rotated into any other frame (having the
same origin) by a single rotation through a given angle and axis.
Hence, instead of given a rotation of a frame by three angles and composing a
rotation matrix out of it, the same rotation can be represented by just one
angle and one axis. The prove of this theorem is based on showing that the
axis in question is actually a unit eigenvector of the corresponding
rotation matrix with respect to the eigenvalue of +1. Hence, it's the vector
which remains untouched by the rotation matrix, fixed in both frames, i.e. it
stays the same after one has applied the matrix on it. For this reason, with
respect to rotations, this vector is called the principle axis of rotation and
the associated angle the principle angle of rotation.
With some mathematics one can compute out of any rotation matrix the principle
axis of rotation and its angle, and vice versa.
By re-reading the sentence above, one may already recognize that what is done
here is essentially the same procedure for converting a rotation matrix to a
quaternion and vice versa, whereas we haven't spoken about the quaternions
just yet.
So if we put the principle rotation axis e = (e1, e2, e3) and angle phi into a
vector, we have
p = (phi, e1, e2, e3).
Now lets compare with a quaternion used for a rotation
q = (q0, q1, q2, q3)
and lets recall that we can transform p into a matrix and a matrix back into
p. Likewise, this is the same procedure with the quaternions. Hence, the
principle axis and angle do somehow reflect the quaternions used for rotation.
But why do we need quaternions for doing rotations than? And why does it even
work? Can't we simply use the principle axis and angle (representing a desired
rotation) to rotate any vector about this axis by the given angle straight?
Unfortunately, we can't. At least not in any standard way.
If we look at any rotation matrix, we know how to rotate any given vector
using such a matrix, i.e. by simply multiplying the vector with the matrix.
But how to rotate a given vector while the rotation is given just as an axis
and an angle? The principle axis and angle together give four parameters
(four-dimensional), but the vector to be rotated has just three dimensions.
Hence, the dimensions won't even match. So how to combine these together to
get a resultant vector being rotated around the principle axis by the given
angle? One might think; what about extending the three-dimensional vector to a
four-dimensional one and to combine them together in some why? Let's see some
examples;
[angle-axis * vector]
(phi, e1, e2, e1) * (0, v1, v2, v3) = does not work.
[angle-axis + vector]
(phi, e1, e2, e1) + (0, v1, v2, v3) = hmm, doesn't work either.
For some reason we have to transform the angle-axis pair to something useful
such that we can combine it with a vector giving the rotated vector as a
result. Well, we already know one solution to this problem, i.e. we can
convert the angle-axis pair to a matrix, but this is computationally very
expensive and would defeat the entire point to begin with.
This is where the quaternions come into play. The field of quaternions,
despite having many other important features, can be used to build so-called
rotation quaternions to transform a three-dimensional vector into a rotated
one by just multiplying them together straight.
The Quaternions and their Imaginary Vector Space
The fundamental insight into the quaternions, being used for rotations, is the
following; Every quaternion is composed of a real number and an imaginary
part. This is similar to the complex numbers, however, the imaginary part of a
quaternion is not one-dimensional, like with the complex numbers (imaginary
axis), the imaginary part of a quaternion is three-dimensional. Hence, the
imaginary vector part of a quaternion lives in a three-dimensional vector
space, an imaginary vector space its elements we will call imaginary
quaternions, i.e. quaternions having no real part. This imaginary vector space
plays an important role for using quaternions for rotation. And the main
insight is that quaternion multiplication encodes rotation and scaling.
For simplicity, let us make a little comparison with the complex numbers.
If one converts two complex numbers into their polar representation, i.e.
z = x+iy into |z|exp(i*phi), which phi the angle the vector (x,y) makes with
the x-axis, and w into |w|exp(i*theta) and multiplying we find z*w = |z|*|w|*
exp(i(phi+theta)). If the norm of w, i.e. |w|, equals 1, i.e. w living on the
unit circle, we have z*w = |z|*exp(i*(phi+theta)). Hence, we can rotate a 2d
vector by transforming it into a complex number while polar-complex-
multiplying it with another complex number living on the unit circle making a
certain angle, the angle we want rotate the vector (x,y) about, with the x-
axis. And given we have z on the unit circle as well, we have z*w = exp(i*(
phi+theta)). This property is essentially a feature of the exponential
function, i.e. translating multiplication into addition, i.e. exp(a)*exp(b) =
exp(a+b). There are many transformation having such useful properties which
are exploited in many applications. For example; the logarithm has a similar
property in turning a multiplication into an addition, i.e. log(a*b)=log(a)+
log(b). Or take the Fourier transformation which translates integration into
multiplication or differentiation into division. For example, with the help
of the Fourier transform, or its cousin the Laplace transform, one can
transform a differential equation into an algebraic one, solving, and
transform back getting the solution.
The way the multiplication is defined for the complex numbers essentially
allows to interpret multiplication as rotation an scaling, which is way
different from standard vector multiplication.
And this same property has its counter partwithin the field of the
quaternions, a four-dimensional vector space. Yet we don't want to rotate a
four-dimensional vector (in contrast with the (x,y) vector above), we just
want to rotate our three-dimensional vector v (now represented as an imaginary
quaternion, i.e. as v = (0,v1,v2,v3)) around a given axis by a given angle.
This restriction from a whole quaternion to only an imaginary one poses some
problems with the multiplication, since the multiplication of a quaternion
with an imaginary one won't necessary give a imaginary quaternion again. Even
worse, multiplying two imaginary quaternions won't necessary give an imaginary
one as well. Hence, the imaginary vector space of the quaternions isn't closed
under multiplication so to speak, i.e. if we equip our usual three-dimensional
vector space (extended with 0 making it four-dimensional and serving as the
imaginary vector space of the quaternions) with the multiplication rule of the
quaternions, then the result of multiplying two imaginary quaternions together
isn't necessarily an imaginary quaternion again. This means the real part of
the resultant quaternion can become non-zero after multiplication and as such
won't represented an imaginary quaternion, i.e. the result lays outside if the
imaginary vector space of the quaternions, i.e. it can become a full
quaternion with a non-zero real part.
Hence, the sole multiplication rule of multiplying two quaternions together
can't solve the problem even if both quaternions are imaginary. However, if
we multiply the result of a quaternion q = (q0,q1,q2,q3) and an imaginary
quaternion v = (0,v1,v2,v3) with the conjugate of q, i.e. (q)^t = (q0,-q1,-q2,
-q3), i.e. q*v*q^t, one can show that the result w, i.e. w = (w0,w1,w2,w3), is
actually an imaginary quaternion, i.e. w = q*v*q^t with w0 = 0. Hence, it is a
vector of our known standard three-dimensional vector space.
Now lets define a mapping Rq by Rq(v) := q*v*q^t. It can be shown that Rq is a
linear isometry for all v over the field of the imaginary quaternions and for
a given fixed unit quaternion q. Now any linear mapping on a finite
dimensional vector space has a matrix representation. The matrix representing
this mapping Rq with respect to a given basis of the quaternions is a rotation
matrix. And it can further be shown that this matrix rotates any imaginary
quaternion v round the imaginary part of q by an angle of 2*phi. This may be
proved by an eigenvalue/eigenvector analysis and can be verified by extracting
the principle axis and angle out of this matrix. It may also be proved by
decomposing the quaternions, i.e. v and q, into their polar-decompositions
each, similar like we can do with complex numbers, and showing that indeed the
resultant w = Rq(v) is a rotation of the imaginary quaternion v (our vector to
be rotated) about the imaginary part of q (our axis of rotation) by an angle
of 2*phi.
Ok, now lets choose a quaternion for q since we already know v (our vector we
want to rotate). Well, lets look at the principle axis and angle from above
again, i.e. p = (phi, e1, e2, e3). Can this already serve as a quaternion with
(e1, e2, e3) being the rotation axis? Nope. The condition for Rq to hold is
that q must be a unit quaternion (norm of q being 1), i.e. q0²+q1²+q2²+q3² =
1. We can normalize p but it won't give the desired rotation axis and angle.
So we need a unit quaternion q which lies on the unit four-dimensional
sphere of the quaternions. Quite interesting, similar to the complex numbers,
we can represent any quaternion as
q = |q|(cos(phi) + qi*sin(phi)),
i.e. its polar decomposition, with qi as a normalized three-dimensional vector
taken out of the imaginary vector space, and combined with the sinus equals
the imaginary part of q, whereas cos(phi) is simply the real part.
Now q is on the unit sphere if the norm of q, i.e. |q|, equals 1, hence, if
q = cos(phi) + qi*sin(phi).
If we use such a quaternion within our mapping Rq, then Rq will rotate any
imaginary quaternion v, and as such any common three-dimensional vector, about
the axis qi by an angle of 2*phi.
But didn't we want to rotate just by phi instead of 2*phi? A simple
substitution of phi with phi/2 yields q = cos(phi/2) + qi*sin(phi/2). Now
the mapping Rq will just rotates by an angle phi. That's why you have to
divide your angle by two while constructing a quaternion for rotation.
Now the principle axis and angle from above (phi,e1,e2,e3), a four-dimensional
vector taken out of our standard four-dimensional real vector space, can be
cast into a four-dimensional unit quaternion as follows;
(phi,e1,e2,e3) -> q = (cos(phi/2),e1*sin(phi/2),e2*sin(phi/2),e2*sin(phi/2)).
Whereas we can't multiply (phi, e1, e2, e3) with any vector v straight to get
the required rotated vector, we can do so with the help of the quaternions,
i.e. with Rq(v) = q*v*q^t. Hence, with the help of Rq we can rotate any three-
dimensional vector around the normalized axis (e1, e2, e3) by an angle phi.
Nice.
Now lets look at
Code:
Quaternion.AngleAxis(90, cam.transform.right) * hit.normal.
This gets translates to
Code:
// angle about to rotate in radians
float phi = 90 * Pi / 180;
// rotation axis
float e1 = cam.transform.right.x;
float e2 = cam.transform.right.y;
float e3 = cam.transform.right.z;
// the defining quaternions
Quaternion q = (cos(phi/2), e1*sin(phi/2),e2*sin(phi/2),e2*sin(phi/2));
Quaternion q^t = (cos(phi/2), -e1*sin(phi/2), -e2*sin(phi/2), -e2*sin(phi/2));
// interpreting hit.normal as an imaginary quaternion
Quaternion v = (0, hit.normal);
// carry out the quaternion multiplication
Quaternion r = q*v*q^t;
// now we just return the imaginary part of r, which is our rotated vector,
// assuming r has components written as r = (w,x,y,z), hence
return Vec3(r.x, r.y, r.z);
The quaternion multiplication can be optimized out by explicitly carrying out
the multiplication and inserting the necessary terms straight into the
unfolded terms. Another optimization is to put the real part in the back of
the vector, i.e. r = (x,y,z,w). Basically, one doesn't even need a quaternion
class at all. Vec4 is enough.
Done.
Some Remarks
Quaternions are more efficient in doing 3d rotation than any matrix can do.
Not only do they need less multiplication in chaining rotations together (yielding
also better precision in the process), they also need less memory, i.e. just 4
floats instead of 9 necessary for a rotation matrix. Another issue stems from
composing rotation matrices together, i.e. they will lose their orthonormal
structure during the process and as such do introduce scaling and shearing.
Hence, a renormalization process is necessary which is computationally *very*
expensive to do. Quaternions do face a similar problem, yet it is sufficient
to re-normalize them. Gimbal lock is another issue to deal with while using
standard rotation matrices composed out of Euler angles. Not so with
quaternions, for they only use one axis of rotation and one angle. So using a
quaternion for rotation can never lead to a lose of a degrees of freedom,
since there is only one axis and this axis will never line up with any other
axis during rotation. It is even a fixed axis. Yet, contrary to common
believe, gimbal lock is also possible with quaternions if one mimics exactly
the process used to compose rotations together using the three Euler angles
and their respective axes to represent a single rotation. For, one can compose
three quaternions, i.e. qx, qy, and qz, representing the rotation about the x-
, y-, and z-axis and their angles, respectively. Applying Rq in sequence, i.e
Rqx(Rqy(Rqz(v))), may also lead to gimbal lock. However, as long as you use
one axis and one angle to represent the intended combined rotation, you are
safe. Quaternions also find application in smooth camera tracking
(interpolation) and can also be used for creating special rotation trick which
are pretty hard to do with Euler angles. Recently, I used quaternions for
computing the attitude of a rigid body (aircraft) out of the angular velocity
by solving a quaternion-differential equation being part of my 6-DOF rigid
body solver. Using quaternions here eases the attitude computation a lot and
also makes it *much more* efficient than Euler angles can ever be.