Quaternions have been one of those concepts I have spent years trying to understand, and I still find them annoying.
I bought books on quaternions. I watched visual explanations. I went through robotics material, rigid-body dynamics material, and the usual “this is actually simple” explanations online. I understood pieces of it, but the concept never fully settled. It always felt like I was borrowing someone else's intuition instead of building my own.

The problem is that I cannot avoid quaternions. If I want to build a flight controller from scratch, I need orientation math that works. A drone is constantly moving between frames: body frame, world frame, sensor frame, gravity direction, thrust direction, camera direction, and estimated attitude. I cannot just hand-wave that away and call a MATLAB function forever.
What bothers me is how this is usually taught. In school and many robotics courses, you can spend weeks or months on Euler angles. You learn roll, pitch, yaw. You learn rotation matrices. You grind through matrix multiplication. You pass exams by getting the final numeric answer the instructor expects. Then, near the end, someone says: by the way, Euler angles have gimbal lock, rotation order matters, and you probably should not use them as your internal representation for serious 3D attitude work.
That is maddening. Rotate around X, then Y, then Z, and you can get a different orientation than rotating around Z, then X, then Y. The sequence matters. The code becomes order-dependent. The logic becomes brittle. Then direction cosine matrices and quaternions get skimmed as if they are a small footnote, when those are the representations you actually need if you want to build something like a quadcopter.
Direction cosine matrices are useful, but they carry nine numbers and must stay orthonormal. Quaternions are compact and stable, but they are unintuitive when you first meet them.
So this post is not me pretending quaternions are easy. It is the opposite. This is me saying: this concept was difficult for me, I still do not like how it is usually explained, but I also know that without understanding it in my own way I cannot move the drone stack forward.
That is why I am turning the math into attitudeMathLibrary: a small C library for drones, attitude control, simulation, and visual debugging.

Why I Care About Understanding It Myself
School and grades optimize for a different thing than building a working flight controller. I have a master's degree in electrical and computer engineering, but I was never someone who cared about grades as the final target. I cared enough to pass, but the optimization function for grades and the optimization function for real understanding are different.
You can be good at Euler-angle problems on paper. You can calculate matrix transformations with numerical precision. You can match the answer key. But when you actually want to design a quadcopter, that is not enough. Now the question is not “can I pass the rotation chapter?” The question is “can I write the attitude math in C, test it, debug it, and trust it inside a flight controller?”
For this project, the optimization function is different. I need to know what orientation means in code. I need to know how to rotate a vector. I need to know how to move from the current orientation to a target orientation. I need to know how to interpolate smoothly between those two orientations over time. And I need tests that prove the convention is what I think it is.
This is also where AI has been genuinely useful. Not because AI magically understands quaternions for me, but because it lets me ask the same “stupid” question ten different ways without worrying about wasting a professor's time or pretending I understood the first explanation. I can keep pushing: what is the axis, what is the angle, what is the reference frame, what does this mean in C, what should the test print, what would a camera tracker need? That loop helps turn a vague mathematical concept into something I can implement, inspect, and harden.
That is why this library exists. I do not want quaternion math to stay as a textbook idea or a beautiful animation I vaguely understand. I want it as C functions, test cases, debug output, visualizers, and eventually flight-controller behavior.
The point is not to sound smart about quaternions. The point is to make a machine know which way it is pointing.
The C Library Behind This Post
The code I am referring to here lives in attitudeMathLibrary. It is a small C library that I am using as one of the foundations for the Antshiv Robotics flight-control stack.
The library is not trying to be a giant robotics framework. It is deliberately focused on the math layer: quaternions, Euler angles, direction cosine matrices, axis-angle rotation, vector rotation, SLERP, and the conversion functions needed to move between those representations.
That matters because a flight controller does not only need a formula. It needs a convention. It needs to know whether a quaternion is stored as [w, x, y, z]. It needs to know multiplication order. It needs normalization rules. It needs tests that print real orientation values, not just a vague “pass.” That is the reason I keep bringing the library into the explanation before getting into the code.
A Quaternion Represents Rotation
In this library a quaternion is stored as:
q = [w, x, y, z]A quaternion has four numbers. That is part of why it feels strange. The drone is not flying in four-dimensional space, so why do we need four values to represent a 3D orientation?
The practical answer is that rotation quaternions live on a unit sphere in quaternion space. For flight-control use, the quaternion should be a unit quaternion:
sqrt(w*w + x*x + y*y + z*z) = 1That rule matters. It is what keeps the quaternion usable as a 3D rotation representation instead of an arbitrary four-number object. In a flight controller, any attitude quaternion should obey this rule, and numerical drift has to be corrected with normalization.
For a unit quaternion, the scalar part and vector part encode rotation. If the rotation angle is θ around a unit axis u, the quaternion can be written as:
q = [cos(θ/2), ux sin(θ/2), uy sin(θ/2), uz sin(θ/2)]The half-angle is one of the parts that feels strange at first. But once you accept that quaternions are composing rotations on a unit sphere, the half-angle stops being magic and becomes part of the representation.
This is the first correction I have to keep reminding myself of: not every arbitrary quaternion is a valid 3D rotation in the way I need for a drone. A unit quaternion used for attitude represents an axis and an angle. The vector part points along the rotation axis, scaled by sin(θ/2). The scalar part stores cos(θ/2). So yes, for the practical flight-controller case, the quaternion is encoding axis plus angle.
The Sphere Intuition: Orientation Versus Rotation

The part that helped me is thinking about the properties of a sphere. A sphere gives you two related ideas at the same time.
- You can define an axis or reference direction on the sphere and describe how that object is currently oriented relative to a fixed frame.
- You can rotate the sphere from one orientation to another by applying a rotation around some axis.
Those sound like different things, but underneath they are both rotations. The difference is the context you attach to the rotation.
In a flight controller, one quaternion might mean: this is the drone's current orientation relative to the world frame. Another quaternion might mean: this is the rotation needed to move from the current orientation to the target orientation.
Same shape. Same four numbers. Different role.
The way I currently think about it is this: an axis-angle value by itself says, “rotate by this much around this axis.” That is a rotation action. Once I pin that same rotation to a fixed reference frame and say this is how the body frame sits relative to the world frame, it becomes an attitude or orientation state.
So orientation is not a totally different kind of math. It is a rotation interpreted from a reference frame. Without the reference frame, I only know the action: rotate around this axis by this angle. With the reference frame, I can say where the drone is pointing. That is the bridge between “rotation” and “attitude.”
q_current: orientation of the drone relative to the world frame
q_error: rotation needed to move q_current toward q_targetThat distinction is exactly why naming matters in C. A variable called q is not enough. I want names like q_current, q_target, q_error, and q_reconstructed because they force the code to say what the quaternion means.
So when I say a quaternion represents orientation, I mean it can describe where the body is pointing relative to a reference frame. When I say a quaternion represents rotation, I mean it can describe the action that moves one orientation into another. The notation is the same, but the interpretation is different. The code has to preserve that distinction.
Axis-Angle Rotation Is The Intuitive Bridge
The library includes axis_angle_rotate. That function is useful because axis-angle is easy to explain: choose an axis, choose an angle, rotate the vector.
The implementation follows Rodrigues' rotation formula. It combines three pieces:
- the original vector scaled by
cos(θ) - the cross product term scaled by
sin(θ) - the axis projection term scaled by
1 - cos(θ)
v_out[i] =
v_in[i] * cos_theta
+ cross[i] * sin_theta
+ axis[i] * dot * (1 - cos_theta);This is a good pen-and-paper topic for a short because it lets you show rotation without immediately jumping into quaternion multiplication.
Quaternion Rotation Is The Runtime Form
Axis-angle is easy to reason about, but the runtime representation I want across the flight stack is quaternion-based. The clean mathematical form is:
v' = q ⊗ v ⊗ q*The vector v is promoted into a pure quaternion:
double v_quat[4] = {0.0, v_in[0], v_in[1], v_in[2]};Then the rotation is applied by multiplying the quaternion, the vector quaternion, and the conjugate. The explicit implementation in the library is intentionally verbose because it is meant to show every intermediate step.
That explicit version matters for teaching. When I explain quaternion rotation on paper or in a YouTube short, I do not want to hand-wave. I want to show the sequence:
- turn vector into pure quaternion
- compute
temp = q ⊗ v - form
q* - compute
result = temp ⊗ q* - read the rotated vector from the imaginary part
The Optimized Path Still Has To Agree
The optimized quaternion_rotate_vector helper does not literally perform the full sandwich product every time. It expands the quaternion terms into rotation matrix elements and applies the matrix to the input vector.
That is faster and cleaner for runtime, but it needs verification. The test suite compares the optimized helper against the explicit helper so the teaching version and runtime version stay aligned.
The test case is simple and useful:
input: [1, 0, 0]
rotate: 90 degrees around Z
expect: [0, 1, 0]That kind of test is small, but it catches a lot: wrong signs, wrong axis convention, wrong multiplication order, or accidental frame inversion.
Camera Tracking: From Current Orientation To Target Orientation
This is where the quaternion math becomes a flight-control primitive. Imagine a camera tracker is following a moving object. The tracking system estimates a target attitude for the drone or gimbal so the camera stays pointed at that object. The flight stack already knows the current attitude. The missing question is:
What rotation takes me from where I am pointing now to where I need to point next?
That is a relative orientation problem. In the library, the helper is:
q_error = q_target ⊗ inverse(q_current)The multiplication order matters. With the convention used in attitudeMathLibrary, applying q_error after q_current reconstructs the target orientation:
q_target ≈ q_error ⊗ q_currentThat gives the controller a clean correction quaternion. If I want a teaching/debug form, I can convert that correction into an axis-angle command:
double q_current[4]; // estimated drone/camera orientation
double q_target[4]; // orientation needed to keep the subject centered
double q_error[4];
if (quaternion_relative(q_current, q_target, q_error)) {
double axis[3];
double angle;
quaternion_orientation_error_axis_angle(q_current, q_target, axis, &angle);
// axis tells the controller which way to turn.
// angle tells the controller how much rotation remains.
}For example, if the camera tracker says the drone needs a 45 degree yaw correction, the relative orientation should become a rotation around the Z axis with an angle of π/4. That exact case is now covered by a unit test. This is the kind of small math helper that looks boring until it prevents an entire flight stack from quietly using the wrong frame convention.
Why The Tests Print Real Values
I do not want tests that only say “pass.” For this kind of math, I want the test to show what happened. The current-to-target test now prints the current quaternion, target quaternion, error quaternion, reconstructed target, Euler-angle interpretation, and axis-angle command.
camera current Euler ZYX deg = roll 0.000, pitch 0.000, yaw 0.000
camera target Euler ZYX deg = roll 0.000, pitch 0.000, yaw 45.000
camera error Euler ZYX deg = roll 0.000, pitch 0.000, yaw 45.000
camera command axis = [0.000000000, 0.000000000, 1.000000000]
camera command angle = 0.785398163 rad / 45.000 degThat output is not just decoration. It is part of how I build confidence. If the current orientation is identity and the target is 45 degrees yaw, the correction should be 45 degrees around the Z axis. If the test cannot explain that clearly, then the test is not helping me understand the system.
Where This Goes Next
The quaternion layer feeds the rest of the Antshiv Robotics stack:
- stateEstimation stores attitude as a quaternion.
- AeroDynControlRig visualizes orientation using OpenGL.
- dynamic_models needs quaternion-aware rigid-body propagation.
- inertial_navigation_system eventually has to orchestrate estimation, dynamics, and control.
So this is not isolated math. This is how the physical machine learns which way it is pointing.