denied.systems

field guide

Rotating coordinate frames and the transport theorem

Rotating frames and the transport theorem - the NED-frame conventions and derivative rules behind body-to-world rotation in a flight estimator.

updated 2026-06-28 state-estimation kinematics

The identity that links inertial and body-frame derivatives β€”

dπͺdt|inertial=dπͺdt|body+π›šΓ—πͺ \left.\frac{d\mathbf{q}}{dt}\right|_{\text{inertial}} = \left.\frac{d\mathbf{q}}{dt}\right|_{\text{body}} + \boldsymbol\omega \times \mathbf{q}

β€” is called the transport theorem. It’s a geometrical fact about rotating coordinate systems, not a physical law. It applies to any vector quantity πͺ\mathbf{q}: position, velocity, angular momentum, a magnetic field vector, whatever lives in 3D space and is measured from a spinning platform.

This note traces it from the concrete picture you already know (body-frame velocity β†’ inertial velocity) back to the general principle.


Start with what you already do in code

In your PID controller’s simulation step, the thrust vector is in body coordinates. To integrate position, you rotate it to the inertial frame using the rotation matrix RR. In body coordinates the quad has velocity [u,v,w]T[u, v, w]^T (from the IMU frame). In inertial coordinates the velocity is [xΜ‡,yΜ‡,zΜ‡]T[\dot x, \dot y, \dot z]^T. The mapping is:

[ẋẏż]=R[uvw] \begin{bmatrix} \dot x \\ \dot y \\ \dot z \end{bmatrix} = R \begin{bmatrix} u \\ v \\ w \end{bmatrix}

This is a rotation matrix β€” a linear algebra operation. It’s the clean, easily-codeable way to do the transform. But there’s another way to see it that reveals the deeper structure.


A simpler picture: the spinning room

Imagine you’re standing in a room that rotates at constant rate Ο‰\omega about a vertical axis (a centrifuge or a merry-go-round).

What the rotating-frame observer sees: you’re standing still at position 𝐫\mathbf{r} relative to the centre. Your velocity in the rotating frame is 𝐯body=0\mathbf{v}_{\text{body}} = 0.

What an inertial observer (someone outside, watching from above) sees: you’re not standing still β€” you’re being carried around in a circle at speed Ο‰r\omega r tangent to the circle. Your inertial velocity is 𝐯inertial=π›šΓ—π«\mathbf{v}_{\text{inertial}} = \boldsymbol\omega \times \mathbf{r}.

If you walk in the rotating frame at 𝐯body\mathbf{v}_{\text{body}}, the inertial observer sees your walking velocity plus the carrying velocity:

𝐯inertial=𝐯body+π›šΓ—π« \mathbf{v}_{\text{inertial}} = \mathbf{v}_{\text{body}} + \boldsymbol\omega \times \mathbf{r}

This is the transport theorem for πͺ=𝐫\mathbf{q} = \mathbf{r}. The π›šΓ—π«\boldsymbol\omega \times \mathbf{r} term is the β€œcarrying term” β€” the velocity you inherit from the frame’s rotation even if you don’t move within it.


Generalising: the theorem for any vector

A vector πͺ\mathbf{q} expressed in a rotating basis πžΜ‚1,πžΜ‚2,πžΜ‚3\hat{\mathbf{e}}_1, \hat{\mathbf{e}}_2, \hat{\mathbf{e}}_3:

πͺ=q1πžΜ‚1+q2πžΜ‚2+q3πžΜ‚3 \mathbf{q} = q_1\hat{\mathbf{e}}_1 + q_2\hat{\mathbf{e}}_2 + q_3\hat{\mathbf{e}}_3

Differentiate in the inertial frame (product rule):

dπͺdt|inertial=qΜ‡1πžΜ‚1+qΜ‡2πžΜ‚2+qΜ‡3πžΜ‚3⏟body derivative+q1πžΜ‚Μ‡1+q2πžΜ‚Μ‡2+q3πžΜ‚Μ‡3⏟frame rotation \left.\frac{d\mathbf{q}}{dt}\right|_{\text{inertial}} = \underbrace{\dot q_1\hat{\mathbf{e}}_1 + \dot q_2\hat{\mathbf{e}}_2 + \dot q_3\hat{\mathbf{e}}_3}_{\text{body derivative}} \;+\; \underbrace{q_1\dot{\hat{\mathbf{e}}}_1 + q_2\dot{\hat{\mathbf{e}}}_2 + q_3\dot{\hat{\mathbf{e}}}_3}_{\text{frame rotation}}

The first group is the body derivative β€” how the components change as seen by someone riding in the rotating frame.

The second group is the contribution from the axes themselves spinning. The fundamental property of angular velocity is that it tells you how a rotating frame’s basis vectors move:

πžΜ‚Μ‡i=π›šΓ—πžΜ‚i \dot{\hat{\mathbf{e}}}_i = \boldsymbol\omega \times \hat{\mathbf{e}}_i

Substitute:

q1πžΜ‚Μ‡1+q2πžΜ‚Μ‡2+q3πžΜ‚Μ‡3=π›šΓ—(q1πžΜ‚1+q2πžΜ‚2+q3πžΜ‚3)=π›šΓ—πͺ q_1\dot{\hat{\mathbf{e}}}_1 + q_2\dot{\hat{\mathbf{e}}}_2 + q_3\dot{\hat{\mathbf{e}}}_3 = \boldsymbol\omega \times (q_1\hat{\mathbf{e}}_1 + q_2\hat{\mathbf{e}}_2 + q_3\hat{\mathbf{e}}_3) = \boldsymbol\omega \times \mathbf{q}

Hence:

dπͺdt|inertial=dπͺdt|body+π›šΓ—πͺ \boxed{\left.\frac{d\mathbf{q}}{dt}\right|_{\text{inertial}} = \left.\frac{d\mathbf{q}}{dt}\right|_{\text{body}} + \boldsymbol\omega \times \mathbf{q}}

That’s the whole theorem. Memorise the structure:

Rotating-frame derivative = body derivative + omega cross the vector


The two applications you care about

1. Position (πͺ=𝐫\mathbf{q} = \mathbf{r}) β€” velocity kinematics

Term Meaning In your code
d𝐫dt|inertial\left.\frac{d\mathbf{r}}{dt}\right|_{\text{inertial}} inertial velocity 𝐯I\mathbf{v}_I [xΜ‡,yΜ‡,zΜ‡][\dot x, \dot y, \dot z] after rotation
d𝐫dt|body\left.\frac{d\mathbf{r}}{dt}\right|_{\text{body}} body-frame velocity 𝐯B\mathbf{v}_B [u,v,w][u, v, w] from IMU
π›šΓ—π«\boldsymbol\omega \times \mathbf{r} carrying velocity from rotation what RR handles in one shot

If the IMU were at position 𝐫\mathbf{r} relative to the quad’s centre of rotation (it is), and the quad is rotating (it is), then the IMU sees a velocity contribution from rotation alone β€” even if the centre isn’t moving. This is why the rotation matrix is applied after computing body-frame motion.

In practice you use the rotation matrix RR rather than the cross product for the position case, because RR handles all three axes at once and is easier to code. But conceptually it’s the same π›šΓ—π«\boldsymbol\omega \times \mathbf{r}.

2. Angular momentum (πͺ=𝐋=Iπ›š\mathbf{q} = \mathbf{L} = I\boldsymbol\omega) β€” Euler’s equations

Term Meaning Equation
d𝐋dt\left.\frac{d\mathbf{L}}{dt}\right. inertial_{\text{inertial}} torque 𝛕\boldsymbol\tau Newton’s second law for rotation
d𝐋dt\left.\frac{d\mathbf{L}}{dt}\right. body_{\text{body}} Iπ›šΜ‡I\dot{\boldsymbol\omega} change in spin, body-frame
π›šΓ—π‹\boldsymbol\omega \times \mathbf{L} π›šΓ—(Iπ›š)\boldsymbol\omega \times (I\boldsymbol\omega) gyroscopic cross-coupling

So:

𝛕=Iπ›šΜ‡+π›šΓ—(Iπ›š) \boldsymbol\tau = I\dot{\boldsymbol\omega} + \boldsymbol\omega \times (I\boldsymbol\omega)

This is euler-body-rate-cross-coupling. The cross term is the same physical effect as the carrying term in the position case β€” the angular momentum vector is being β€œcarried around” by the body’s rotation.


Why the cross term feels different for position vs.Β angular momentum

Same mathematical structure, but:

This self-interaction is precession. Hold a spinning bicycle wheel by its axle and try to tilt it β€” the wheel fights you by turning in a perpendicular direction. That resistance is π›šΓ—(Iπ›š)\boldsymbol\omega \times (I\boldsymbol\omega) manifesting in your hands as a real torque.


Applied to your quadcopter simulation

Your current PID code likely does:

// Body torques from motor mixing
float tau_x, tau_y, tau_z;

// Angular acceleration (simplified)
float p_dot = tau_x / Ixx;
float q_dot = tau_y / Iyy;
float r_dot = tau_z / Izz;

// Integrate
p += p_dot * dt;
q += q_dot * dt;
r += r_dot * dt;

The transport theorem says this is missing the term π›šΓ—(Iπ›š)\boldsymbol\omega \times (I\boldsymbol\omega). For a near-hover quad with small p,q,rp, q, r, the missing term is small. For aggressive manoeuvres it’s not. The full integration is:

float p_dot = tau_x / Ixx + (Iyy - Izz) / Ixx * q * r;
float q_dot = tau_y / Iyy + (Izz - Ixx) / Ixx * p * r;
float r_dot = tau_z / Izz + (Ixx - Iyy) / Izz * p * q;

Those extra terms are the transport theorem at work on the angular momentum vector. Nothing more.

This C code belongs in the physics model of a flight simulator β€” one that currently drops these terms, and this note is the reference for adding them when the flight regime demands it.


Summary


See also: euler-body-rate-cross-coupling β€” when you can drop the cross term and when it bites you.