So, I've been writing this raytracer for my own entertainment and recently I discovered Quaternions so I decided to implement a camera that uses them. After a lot of struggle I finally managed to create a clear Quaternion class and a Camera that uses it to represent it's orientation. The camera only has a Vec3 which is it's position and a single Quaternion that represents it's orientation.
Now, I've seen this being done in professional game engines like Unity so I knew it was possible, but after I had written the calculations using my own little fingers and I tried it and it just worked my mind was just blown away and I laughed for at least a minute straight. I did not believe it would work but it just did, the program was calculating the up, forward and right vectors from the single Quaternion on the fly while I was rotating the camera and yaw, pitch and roll all worked beautifully...
I've been looking for a good explanation about this with no luck and I don't know really where else I could ask about this so I'll try my luck here. Sorry if this has been answered here before, I tried to search for quite a while with no luck.
public Quaternion createFromAxisAngle(float x, float y, float z, float theta)
{
theta = (float) Math.toRadians(theta);
this.w = (float) Math.cos(theta / 2.0);
this.x = x * (float) Math.sin(theta / 2.0);
this.y = y * (float) Math.sin(theta / 2.0);
this.z = z * (float) Math.sin(theta / 2.0);
return this;
}
public Quaternion mul(Quaternion q)
{
Quaternion r = new Quaternion();
r.w = w * q.w - x * q.x - y * q.y - z * q.z;
r.x = w * q.x + x * q.w + y * q.z - z * q.y;
r.y = w * q.y - x * q.z + y * q.w + z * q.x;
r.z = w * q.z + x * q.y - y * q.x + z * q.w;
return r;
}
public Quaternion mul(Vec3f v)
{
Quaternion r = new Quaternion();
r.w = -x * v.x - y * v.y - z * v.z;
r.x = w * v.x + y * v.z - z * v.y;
r.y = w * v.y + z * v.x - x * v.z;
r.z = w * v.z + x * v.y - y * v.x;
return r;
}
public Vec3f getForwardVector()
{
return new Vec3f(0, 0, 1).mul(this);
}
public Vec3f getUpVector()
{
return new Vec3f(0, 1, 0).mul(this);
}
public Vec3f getRightVector()
{
return new Vec3f(1, 0, 0).mul(this);
}
Those are the methods I'm using taken straigth out from my Quaternion class, I understand the idea behind createFromAxisAngle but those multiplication operations are something I can't comprehend but I can accept them as facts. The thing I can't believe is that how can those getForwardVector() and so on calculations just work? It's just mindblowing...
This is what I'm doing in there to get the forward vector for example:
forward = Q_cam_eye * vec3(0, 0, 1) * Q_cam_eye_conjugate
And that's it. That gives me the forward direction vector from a chosen Quaternion... It just uses those two multiplication methods I've described above, nothing else really. My wild guess is that the information about the local x, y and z axes of a quaternion is somehow hidden into the imaginary parts of it? I don't really have any idea about that though, which is why I'm asking for a somehow understandable explanation about it here.
Thank you in advance. Sorry about the not-so-mathematical text, I'm more of a programmer than a mathematician.
One short explanation is that your axes don't actually hold as much information as you think they do. At first blush, it looks as though it's impossible for a quaternion (which has four 'parts': its real component and its $i$, $j$ and $k$ components) to hold all the data for a coordinate frame (which is three vectors each with three coordinate components, a total of 9 real values). But in fact, those real values are much more constrained than they look:
And from here, it looks like we're done. But wait — I said a quaternion has four parts, but that's not really true; the same argument about unit vectors applies to unit quaternions, so really our quaternion is only holding 3 values. What gives? Well, I said that our $\vec{x}$ and $\vec{y}$ axes had two pieces of data each — but they also have one constraint on them, that $\vec{x}\cdot\vec{y}=0$ (in other words, they're orthogonal). This lets you shave one more value out of the puzzle (another way of seeing this is to see that once you have your $\vec{x}$ axis, then all of the possible $\vec{y}$ axes lie in a circle orthogonal to the $\vec{x}$ axis, so you can essentially describe your $\vec{y}$ axis with just an angle) and leaves you with only three real values necessary to extract the $\vec{x}$,$\vec{y}$ and $\vec{z}$ axes of a frame, a perfect match for the amount of data a unit quaternion holds.
I'm skipping over a lot of fine technical details in all of this (in particular, the notion of what a 'part' is - more formally I'd be talking about the dimension of all of the relevant spaces) but if you want to know more about it (including why a quaternion $\mathbf{q}$ and its negation $-\mathbf{q}$ represent the same rotation and how this is related to the 'belt trick' or 'butler trick' and even to why you have to loop a car's sunshade into thirds rather than in half!) then I encourage you to look up the Special Orthogonal Group $SO(3)$ - there's a lot more to this than meets the eye, and more than I can briefly describe here.