Michael Isner's 10th Level Magic User Quaternion Spells
(Solving rotation problems in XSI 2.0 or greater)
Contents:
Visualizing Rotations
Comparison of Rotation Representations
Using Quaternions without scripting
Techniques to Visualize Quaternion Operations
Extracting Quaternions from Euler
Reading Quaternions Directly
Adding 2 Quaternions
Blending 2 Quaternions
Reversing Rotations
Subtracting Quaternions
Scaling Rotations
Constraining Rotations to a plane
Most peoples first thought of a rotation is either: a general concept of how something is angled, where something is points, how something like a basketball is oriented or even a the (x,y,z) coordinates of a rotation. None of these are really precise enough to be productive with rotations. To solve problems with rotations you really need to be clear in your head about what you are dealing with.
So to be specific, lets look in detail at the properties of rotations. This approach is not the only way you can picture a rotation, but it's the method I've found the best to understand rotations and solve rotation problems.
If you want to track a rotation with your
eye, you usually fix on a single point to follow it. This point on a unit
sphere, from here on will be called a "Unit Point".
You can picture as that green pyramid rotates around, that blue Unit Point dot
will move around the surface. But the Unit Point alone, is really not
enough to describe a rotation. That object could point at the same unit
point and spin around 360°.
If you picture you arm reaching out to a specific point, you still have a full
degree of rotation at that position.
So to completely describe a rotation, you need to take into account the
orientation of the Unit Sphere.
So the blue Unit Arrow here is full
describing a rotation. The arrow has a position on a unit sphere and 360°
range it can rotate around from that point.
A rotation is that blue arrow. When you visualize the rotation of an
object, picture an arrow on a sphere surrounding it.
Now rotations have one other small point we need to take into consideration.
Rotations have a zero starting point on that sphere:
So you can think of any rotation as an animation on the surface of a sphere between two arrows: the zero arrow and your rotation. The zero arrow is important, because it changes between different transformation spaces (for example between global and local).
If you think about rotation as a motion, it also becomes clear that rotations are very much like vectors: they can be either a delta (or difference) between rotations or description of an orientation. The zero rotation is the reference that your rotation is against.
2) Comparison of Rotation Representations:
b)Euler
Rotations
I tend to think of Eulers more as a method of generating rotations
than an accurate way to describe them spatially.
The generation is done by three axis and angle rotations, one after another.
One way to imagine it as grabbing a basketball, and doing 3 pushes to attempt to
align the ball with your desired rotation. If
your Euler rotation is (45, 35, -20) it really means take something at 0,0,0
add:
Euler angles are pretty useless for blending and solving rotation problems because the combinations of rotations can easily produce a situation where two of the axis are very close in alignment and begin to double up the rotation being added. It's a very common thing for someone first learning about rotation expressions to spend alot of time pulling their hair out because no matter what they do they cannot avoid gimble lock. Any approach they take will be intrinsically flawed from the start because the are using an rotation representation that is not up for the task they are trying to achieve.
There are a couple reasons why Euler
representations are popular:
1) They can be easily represented as a set of 2D projections (and therefore
fcurves) so animators like them because it provides a mechanism to easily modify
and see acceleration. Really animators are not manipulating a full
description of a rotations at
all, they are just masters of adding tiny sequential rotation pushes.
2) Mechanical objects that rotate on a single axis like helicopters or camera
rigs can be represented well by Euler angles. This is really because those
objects are physically constructed in a manner identical to the transformation
done in Euler, essentially 3 sequential axis and angle transformations.
b) Constraints and up Vectors
Another way of describing rotations in XSI is
with constraints and up vectors. This is the bread and butter of the way
people are currently setting up characters. The diagram below shows a
direction constraint.
There are many constraint systems that only give us a unit point: Direction, Path with Tangency On and an IK chain. By this description alone, there is enough information to know where the rotation is pointing, but not enough to get a Unit Arrow.
Using constraints it is another vector
that is used to complete the rotation description. This vector defined by
an UpVector Constraint. Basically what is it doing is making another Unit
Point and making your unit Arrow angle towards that point.
So you can see there's almost a complete system for describing a rotation here. Where it breaks apart is the fact that there is no solution when the Upvector is the same or the opposite of the direction vector. It is also really tricky to control when the vectors are even close. Basically this is a rotation system with two poles described by the proximity of those vectors. Even if you use a direction constraint or IK chain in XSI and don't define an upvector, you are using one and will notice the "pole" behavior. It is just positioned by default at y+ and can be found in the Upvector tab of any constraint.
Keep in mind though, there really is not such thing as flipping. Rotations are always doing what you tell them, it is always the limits of your rotation system that causes unwanted behavior.
d) Axis and Angle
Another way to view a rotation is axis and angle notation:
Axis and Angle notation describes the single axis rotation that will rotate the zero arrow into a unit arrow. It has two components: a vector that describes the axis or plane that you are rotating on, and the angle you rotate through that axis. The rotation in the diagram above could be described as (axis.x, axis.y, axis.z, 80). You can get and set axis and angle values in XSI through the Object Model, from both an SITransformation and an SIRotation object.
Really when you use Euler rotations you are just doing 3 axis and angle transformations where the axes are the global (or local) x,y,z vectors.
I think this way of representing rotations
is very appealing to humans because it is very similar to the way we physically
manipulate rotations with our hands. When someone rotates a Ferris wheel or a
basketball, the generally grab it and rotate on a single axis.
The problem is though, that this is not a very good system to blend or transform
rotations. You can see the path from the zero arrow to the unit arrow is
really the long way about. There is a much blend between the two
that is rotating and spinning at the same time.
c) Unit Quaternions
I think the best way to visualize a unit quaternion (the kind used in XSI), is
exactly the same as a rotation. Take a look over Section 1 "Visualizing
Rotations". These are the exact properties of quaternions. The main
feature of quaternions is that they blend together well. Working with
quaternions to solve rotation problems is much closer to working with vectors
for translations, once you get the hang of it there are not all the painful
and tricky problems found in other rotation representations.
I think people can quickly get in over their head with quaternions when they try
to divide it into components. There is no need to do this to solve
rotation problems in XSI. You are provided with a full set of tools to
manipulate quaternions and these can do a great deal without any need for
digging deeper into the mathematical innards. The reset of this article
will cover how to study, understand and use these tools.
But if you're curious, here are a few facts about quaternions:
1) Like axis and angle, quaternions are
composed of a vector and a scalar value. The difference with quaternions
is that each component of the vector is a complex (imaginary) number.
2) Complex number are written in the form a + ib, where i2
= -1. They were first used to solve the problem a2
+ b2 = 0.
3) Don't waste your time trying to come up with an interface that manipulates
(or animates) the w, x, y, z values individually. The result is
unpredictable because the components are woven together in the formula:
[cos(a/2), sin(a/2) * nx, sin(a/2)* ny, sin(a/2) * nz]
where:
a is the angle of rotation and <nx,ny,nz> is the axis of
rotation.
3) Using Quaternions without scripting
a) At the core of XSI, all rotations are manipulated as quaternions. From the UI, you can read and set but not animate quaternion values. To read them open the Explorer and change the mode to "All Nodes(Scripting)". From here if you look under Global or Local Orientation you can see there's a folder of "quat" channels. If you want to read an change them, just create a custom parameter set and drag and drop them on it as proxy parameters. I recommend dropping the local, because you get a much better slider range to test manipulating them with.
b) By default the animation mixer will mix
all rotations as quaternions (this can be switched to Euler in the mixer
properties tab). If you want to do smooth quaternion blended animation
just save poses of your rotations (or your entire character) and blend them
together in the mixer. Blending between expressions, constraints and keys
will also be blended with quaternions.
c) Constraint blending (outside the mixer) also uses quaternions. For
example if you do two orientation constraints, and set the blend weight to .5 do
a 50% slerp between the two orientations.
d) If you load the default rig or use the bi-ped proportional guide to generate a character, the spine is quaternion blended.
4) Techniques to Visualize Quaternion Operations
I've constructed a scene to make it easy
to understand the relationship between quaternion inputs and outputs.
Here's the scene: quat_viewer_03
You can write a quaternion scripted operator on the Global Orientation of the Blue Object. As inputs you can read the quaternions off of the green and yellow objects.
For an interface, you just grab the yellow and green arrow inputs and slide them around in view mode. They will remain constrained to the surface of the sphere. The blue arrow will show the Blue Arrow corresponding with the orientation of the blue object.
I've found this technique for studying quaternions to be very successful. I felt like I was working blind before I created this, and since I've found it very easy and quick to solve rotation problems. I also hook up sliders in the scripted operators so I can study how my different variables will animate.
The sphere and the arrows are subdivision surfaces, so if you're on a slow machine and want to speed things up you can reduce their subdivision. It's also much faster to interact with the sphere if you expand this user view to full.
If you look at the scripted operator you
can see to keep things simple I've kept the variable names A, B and C. A
is the green, B is the yellow and C is the blue output.
Relative Zero Arrow:
The one thing to keep in mind when working with this scene and working with quaternions in general is that the zero arrow is arbitrary. You can think of it as the location you eye is fixed on as you watch a rotation. In fact, every point on the surface is being moved by a given rotation transformation, but to watch it you really need a point (in fact arrow) of reference.
In this particular scene the tip of the
cones are pointed Y up. So the zero point is located with the vector
(0,1,0) and the arrow is pointing in alignment with the x axis (1,0,0).
This is not significant for most quaternion operations (which are blends and
additions of relative rotations themselves), but will be significant if you want
to spatially manipulate your arrow in relation to a non-rotational reference
(for example constraining rotation to a single plane).
Although quaternions can be read and manipulated through C++, scripting and scripted operators, I'm going to solely focus on scripted operators for this document. The reason for this is that it allows you to watch rotation behavior interactively and they are very simple to create and modify.
As I said earlier, it is possible to read
quaternions directly in XSI, but you need to write to write to Euler (this is
for scripted ops, through scripting you can write to a full transform). To
start things off though I'm going to show how to read in an Euler of Object
A, convert to Quaternion, then right back to Euler on Object B:
You can load the scene here:
quat_to_euler_01
The scripted operator is located Object B > Global >
Euler.
-------------INPUTS AND
OUTPUTS---------------------
Cx, Cy, Cz
Output C.kine.rotx,roy,rotz
Ax, Ay, Az Input
A.kine.rotx,roy,rotz
------------------MAIN PANE-----------------------------
Select Case Out.Name
case "cx"
' get a rotation while
converting degree inputs into rotations
Arot.SetFromXYZAnglesValues XSIMath.DegreesToRadians(ax),
XSIMath.DegreesToRadians(ay), XSIMath.DegreesToRadians(az)
Crot.GetXYZAngles C
'from the rotation get a
quaternion
Arot.GetQuaternion Aquat
'at this point you are holding
the quaternion Aquat and can begin to manipulate it.
'now output the quaternion into Euler angles
Crot.SetFromQuaternion Aquat
Crot.GetXYZAngles C
Out.Value = xsimath.radianstodegrees(C.x)
case "cy"
Out.Value = xsimath.radianstodegrees(C.y)
case "cz"
Out.Value = xsimath.radianstodegrees(C.z)
end select
-----------------BOTTOM
PANE--------------------------
dim Arot, Aquat, Crot, C
set Arot = xsimath.createrotation
set Crot = xsimath.createrotation
set Aquat = xsimath.createquaternion
set C = xsimath.createvector3
-------------------------------------------------------------
So before I go further I'll make a few
points about making scripted operators, and this operator in particular:
1) It's faster to keep your variables that are objects like Rotations and
Quaternions as global operators. This means define them in the bottom
pane. Making the objects over and over again in a loop will degrade
performance.
2) Most of the object manipulation I'm doing is with the Object Model.
The best way to get used to using these is to get used to the help for these
objects. Click on the ? in the scripting window and look under: Reference
> Reference Entities > Objects > SIQuaternion, SIRotatation,
SITransformation, SIVector, SIMatrix4 and XSIMath.
3) Although rotations are described in the interface of XSI in degrees, the are
described internally and in the Object model in radians. That's why you
need to use the conversion routines xsimath.DegreesToRadians and
xsimath.radianstodegrees at the beginning and end of this function.
4) Rotation calculations need to be done in a single block for all three Euler
angles. That's why this operator calculates all three rotation values in
the evaluation of cx and then carries them over to be written on cy an cz.
5) When connecting your inputs and outputs to a scripted operator, just drag and
drop them from the explorer.
So in fact the last operator we wrote can be a bit cleaner, you can directly read the quaternion values instead of picking up the Euler values. Here's the revised version of the scene: read_quat_01
-------------INPUTS AND
OUTPUTS---------------------
Cx, Cy, Cz
Output C.kine.rotx,roy,rotz
Aw, Ax, Ay, Az Input
A.kine.quatw, quatx, quaty, quatz
------------------MAIN PANE-----------------------------
Select Case Out.Name
case "cx"
Aquat.set aw, ax, ay, az
Crot.SetFromQuaternion Aquat
Crot.GetXYZAngles C
Out.Value = xsimath.radianstodegrees(C.x)
case "cy"
Out.Value = xsimath.radianstodegrees(C.y)
case "cz"
Out.Value = xsimath.radianstodegrees(C.z)
end select
-----------------BOTTOM
PANE--------------------------
dim Aquat, Crot, C
set Crot = xsimath.createrotation
set Aquat = xsimath.createquaternion
set C = xsimath.createvector3
-------------------------------------------------------------
You can see we saved quite a bit of hassle at the beginning reading the quat's directly. This will be the base we use to manipulate quaternions.
So now lets begin to tackle quaternion
problems. The first is if you want to add to rotations together. A
typical example of this is if you want an object that was a "double child" of
two objects and inherited rotations from both of them.
Open the scene: add_quats_01
-------------INPUTS AND
OUTPUTS---------------------
Cx, Cy, Cz
Output C.kine.rotx,roy,rotz
Aw, Ax, Ay, Az Input
A.kine.quatw, quatx, quaty, quatz
Bw, Bx, By, Bz Input
B.kine.quatw, quatx, quaty, quatz
------------------MAIN PANE-----------------------------
Select Case Out.Name
case "cx"
Aquat.set aw, ax, ay, az
Bquat.set bw, bx, by, bz
Cquat.Mul Aquat, Bquat
Crot.SetFromQuaternion Cquat
Crot.GetXYZAngles C
Out.Value = xsimath.radianstodegrees(C.x)
case "cy"
Out.Value = xsimath.radianstodegrees(C.y)
case "cz"
Out.Value = xsimath.radianstodegrees(C.z)
end select
-----------------BOTTOM
PANE--------------------------
dim Aquat, Bquat, Cquat, Crot, C
set Aquat = xsimath.createquaternion
set Bquat = xsimath.createquaternion
set Cquat = xsimath.createquaternion
set Crot = xsimath.createrotation
set C = xsimath.createvector3
-------------------------------------------------------------
One last thing to keep in your mind when adding rotations (multiplying quaternions) is that it is not the same to add A + B as it is to add B + A. This is know as rotations being non-commutative.
If you think of a rotation as a kind of path with a spin on it, add when you add you do so by matching the orientation of your last spin, this does make sense.
One of the greatest advantages of using
quaternions is they are easy and efficient to blend using a slerp function.
To make C a 50% blend between A and B, just:
C.slerp A, B, .5
The best way to setup a slerp that slides
interactively from 0 to 1 is to setup a slider as input for your scripted
operator. To do that:
a) go to the connections pull-down in your scripted op.
b) Hit "New" to create a new slider. For this example lets call
it "mySlider".
c) read it in your scripted op with the line
myVariable = In_UpdateContext.mySlider.Value
One tip I've figured out using sliders: you will get better performance if you use a slider outside the operator, but as of 2.0 it is difficult to hook up such sliders through presets.
So here's an example Scene:
slerp_01
-------------INPUTS AND
OUTPUTS---------------------
Cx, Cy, Cz
Output C.kine.rotx,roy,rotz
Aw, Ax, Ay, Az Input
A.kine.quatw, quatx, quaty, quatz
Bw, Bx, By, Bz Input
B.kine.quatw, quatx, quaty, quatz
---------------------SLIDERS-------------------------------
mySlerp Double
range 0 to 1
default .5
------------------MAIN PANE-----------------------------
Select Case Out.Name
case "cx"
mySlerp = In_UpdateContext.mySlerp.Value
Aquat.set aw, ax, ay, az
Bquat.set bw, bx, by, bz
Cquat.Slerp Aquat, Bquat, mySlerp
Crot.SetFromQuaternion Cquat
Crot.GetXYZAngles C
Out.Value = xsimath.radianstodegrees(C.x)
case "cy"
Out.Value = xsimath.radianstodegrees(C.y)
case "cz"
Out.Value = xsimath.radianstodegrees(C.z)
end select
-----------------BOTTOM
PANE--------------------------
dim Aquat, Bquat, Cquat, Crot, C', mySlerp
set Aquat = xsimath.createquaternion
set Bquat = xsimath.createquaternion
set Cquat = xsimath.createquaternion
set Crot = xsimath.createrotation
set C = xsimath.createvector3
-------------------------------------------------------------
9) Reversing Rotations
Many rotation operations you do will
require you to get a certain rotation, but backwards.
To get this all you need to do is Invert the Quaternion.
Here's an example scene: Invert_01
There are two methods in the OM for
quaternions to do this:
SIQuaternion.Invert SourceQuat
or
SIQuataernion.InvertInPlace
So next we will look at subtracting one
rotation from another. There are many applications for this, a couple are:
a) To find the offset or delta between to rotations so the same transformation
can be applied to other objects.
b) To figure out angular velocity between updates.
c) To divide rotations into parts so the components can be manipulated
differently.
and many others....
So to get the reverse of a rotation, we
just have to keep in mind where we are coming from and where we are going to.
So to get from A to B we need to Inverse(A) * B
If we wanted this new rotation C, to be described by the Object Model:
A.InvertinPlace
C.mul A, B
and because we are multiplying quaternions the
opertation is non-commutative.
Here's a scene showing the behavior:
sub_quats_01
-------------INPUTS AND
OUTPUTS---------------------
Cx, Cy, Cz
Output C.kine.rotx,roy,rotz
Aw, Ax, Ay, Az Input
A.kine.quatw, quatx, quaty, quatz
Bw, Bx, By, Bz Input
B.kine.quatw, quatx, quaty, quatz
------------------MAIN PANE----------------------------
Select Case Out.Name
case "cx"
Aquat.set aw, ax, ay, az
Bquat.set bw, bx, by, bz
Aquat.InvertInPlace
Cquat.Mul Aquat, Bquat
Crot.SetFromQuaternion Cquat
Crot.GetXYZAngles C
Out.Value = xsimath.radianstodegrees(C.x)
case "cy"
Out.Value = xsimath.radianstodegrees(C.y)
case "cz"
Out.Value = xsimath.radianstodegrees(C.z)
end select
-----------------BOTTOM
PANE--------------------------
dim Aquat, Bquat, Cquat, Crot, C
set Aquat = xsimath.createquaternion
set Bquat = xsimath.createquaternion
set Cquat = xsimath.createquaternion
set Crot = xsimath.createrotation
set C = xsimath.createvector3
-------------------------------------------------------------
11)
Scaling Rotations
So now that we have a library of methods to
generate rotation behaviors we can start to pull them together into larger
tools.
One such challenge is to take a given rotation and scale it 3 1/2 times.
It's easy enough to scale rotations between 0 and 1 times their original values
(just slerp them with an empty or zero quat), but it's harder to scale over 1.
So if adding rotations is done by multiplying quaternions, the solution I use is
a loop of quaternion multiplying. If I want to scale my current rotation by 3.5, I
multiply the quaternion by itself 3 times and 4 times and do a slerp of .5 of the two
results.
Here's a scene that does this multiplying loop:
scaling_quats
-------------INPUTS AND
OUTPUTS---------------------
Cx, Cy, Cz
Output C.kine.rotx,roy,rotz
Aw, Ax, Ay, Az Input
A.kine.quatw, quatx, quaty, quatz
---------------------SLIDERS-------------------------------
slider Double
range 0 to 5
default
------------------MAIN PANE-----------------------------
Select Case Out.Name
case "cx"
slider = In_UpdateContext.slider.Value
Aquat.set aw, ax, ay, az
scale_quat Aquat, slider, Cquat
Crot.SetFromQuaternion Cquat
Crot.GetXYZAngles C
Out.Value = xsimath.radianstodegrees(C.x)
case "cy"
Out.Value = xsimath.radianstodegrees(C.y)
case "cz"
Out.Value = xsimath.radianstodegrees(C.z)
end select
-----------------BOTTOM
PANE--------------------------
dim Aquat, Cquat, Crot, C
set Aquat = xsimath.createquaternion
set Cquat = xsimath.createquaternion
set Crot = xsimath.createrotation
set C = xsimath.createvector3
set highQuat = xsimath.createquaternion
set MulQuat = xsimath.createquaternion
'------------------------------------------
' Scale Quat
'------------------------------------------
' to multiply our rotation by 4.3 we need to get the
' lowerInt(4), the upperInt(5) and slerp between them the
' remainder (.3).
'..........................................
function scale_quat(in_d, in_slider, MulQuat)
dim i, remainder
set lowQuat = xsimath.createquaternion
if in_slider >= 1 then
'find the Quat from the lower Int(lowQuat)
for i = 0 to int(in_slider) - 1
lowQuat.MulInPlace in_d
next
end if
'find the Quat from the upper Int(highQuat)
highQuat.Mul lowQuat, in_d
'now slerp the remainder
remainder = in_slider - Int(in_slider)
MulQuat.slerp lowQuat, highQuat, remainder
end function
'------------------------------------------
-------------------------------------------------------------
12)
Constraining Rotations to a plane
So here's the final rotation manipulation
for this article: constraining rotations to a plane. It's not really a
quaternion manipulation we are doing, but it comes up enough I figured it should
be covered.
To perform this projection, we have to go back a bit and take into account that
our placement of the Unit Point (and Unit Arrow) are arbitrary. But it is
this arbitrary vector that we'll use to define the our axis of projection, and
to define the plane we are constraining to (ie it is the normal of the plane to
constrain to). Here's an outline of the
process I use:
1) Get the Vector to the Zero Point, in
all my example scenes it's y up so 0,1,0.
2) Transform that vector with your current rotation to get the Unit Point (this
is your arrow, but without any spin).
3) Find the angle between the two vectors. The "equator" around your zero
vector will have an angle of 90°.
So you can find the angle remaining to hit that plane by subtracting it from
90°.
4) Find the axis for your extra rotation which is the normal of the plane
defined by your two vectors. Get this from a cross-product.
5) Build your new rotation to reach the equator.
6) Add on the new rotation by multiplying it with your current.
Of course there's no good answer for this
when your Zero Point equals you Unit Point.
Here's a scene that shows this behavior:
Constrain_to_plane_03
-------------INPUTS AND
OUTPUTS---------------------
Cx, Cy, Cz
Output C.kine.rotx,roy,rotz
Aw, Ax, Ay, Az Input
A.kine.quatw, quatx, quaty, quatz
------------------MAIN PANE----------------------------
Select Case Out.Name
case "cx"
slider = In_UpdateContext.slider.Value
Aquat.set aw, ax, ay, az
zeroPoint.set 0,1,0
'transform zeroPoint to become the unit point
Trans.SetTranslation zeroPoint
Atrans.SetRotationFromQuaternion Aquat
Trans.MulInPlace Atrans
Trans.GetTranslation unitPoint
' get the cross product (vector perpendicular to the plane
defined by
' two vectors) and the angle between the unit and zero
vectors
angle = zeroPoint.angle(unitPoint)
cp.cross zeroPoint, unitPoint
'now we can make a vector to the unit point with no extra
spin
'so to get to the xz plane we need an angle of 90, so get the
missing amount
angle = xsimath.degreestoRadians(90) - angle
extraRot.SetFromAxisAngle cp, angle
Arot.SetFromQuaternion Aquat
Crot.Mul Arot, extraRot
Crot.GetXYZAngles C
Out.Value = xsimath.radianstodegrees(C.x)
case "cy"
Out.Value = xsimath.radianstodegrees(C.y)
case "cz"
Out.Value = xsimath.radianstodegrees(C.z)
end select
-----------------BOTTOM
PANE--------------------------
dim Aquat, Cquat, Crot, C, zeroPoint, unitPoint
dim angle, cp, extraRot
set zeroPoint = xsimath.createvector3
set unitPoint = xsimath.createvector3
set cp = xsimath.createvector3
set C = xsimath.createvector3
set trans = xsimath.createtransform
set atrans = xsimath.createtransform
set extraRot = xsimath.createrotation
set Aquat = xsimath.createquaternion
set Cquat = xsimath.createquaternion
set Crot = xsimath.createrotation
set Arot = xsimath.createrotation
-------------------------------------------------------------
So if you look closely at this it should
become clear you can use this technique to project onto any arbitrary plane you
want.
Credits: And finally special thanks to Brent McPherson, Mathieu Mazerolle, and Marie-Claude Frasson for teaching me about quaternions, and being very patient while I asked millions of questions :)