Rotation Conventions Cheat Sheet — quaternion order, Euler sequences, by library

Which libraries use xyzw vs wxyz quaternions, intrinsic vs extrinsic Euler angles, and which coordinate handedness — a reference table for ROS, Eigen, SciPy, MuJoCo, and URDF, so you stop silently swapping conventions when porting rotations between tools.

By Rahul Rajelli · updated 2026-07-10 · RSS

The single most common rotation bug isn't a math error — it's moving a rotation between two libraries that disagree on convention while looking identical on the page. Every row below produces a different, valid-looking rotation for the same four or three numbers. When in doubt, use the rotation converter's live 3D gizmo to check visually rather than trust the numbers.

Quaternion component order

Library / ecosystemOrderNotes
ROS (geometry_msgs/Quaternion, tf2)x, y, z, wThe field order in the message definition itself
glTFx, y, z, wSpecified explicitly in the glTF 2.0 spec
SciPy (scipy.spatial.transform.Rotation)x, y, z, wRotation.from_quat([x,y,z,w]) — easy to get backwards coming from Eigen
Eigen (C++)w, x, y, zEigen::Quaterniond(w,x,y,z) constructor order — but .coeffs() returns x,y,z,w. Both orders exist in the same library, which is exactly the trap.
MuJoCo (XML quat attribute, mjData)w, x, y, zDocumented MuJoCo convention throughout the C API and XML schema
Unity (UnityEngine.Quaternion)x, y, z, wAlso left-handed, Y-up — the coordinate system differs from ROS/URDF, not just the quaternion field order (see below)

Verify against your specific library version before trusting this table for anything safety-relevant — component order is usually stable within a library but has changed historically in a few (Bullet physics being a known example), and this table can't track every release.

Euler angles: intrinsic vs extrinsic

Intrinsic ("rotating frame"): each rotation is about the body's own axes, as reoriented by the previous rotation. Extrinsic ("fixed/static frame"): every rotation is about the original, unmoving world axes. The same three numbers and the same axis order (say, X-Y-Z) give a different final orientation depending on which one you mean — they are not interchangeable, and "roll, pitch, yaw" alone doesn't disambiguate.

ContextConvention
URDF <origin rpy="">Extrinsic X→Y→Z (fixed-axis) — verified in this site's own SDF/DH converters against an independent matrix implementation
SciPy Rotation.from_euler()Explicit either way: uppercase sequence (e.g. 'XYZ') = intrinsic, lowercase (e.g. 'xyz') = extrinsic — the cleanest API of any library on this list, precisely because it forces you to say which one you mean
Aerospace / many robotics textbooksOften intrinsic Z→Y→X ("yaw, pitch, roll", written 3-2-1) — different axis order AND different frame convention from URDF's rpy, despite both being called "roll-pitch-yaw" in prose

There are 12 valid axis sequences total (6 Tait-Bryan: XYZ, XZY, YXZ, YZX, ZXY, ZYX; 6 proper Euler with a repeated axis: XYX, XZX, YXY, YZY, ZXZ, ZYZ), each readable as intrinsic or extrinsic — 24 combinations. The rotation converter supports all of them explicitly rather than assuming you mean XYZ.

Coordinate handedness & up-axis

EcosystemHandednessUp axis
ROS / URDF / Gazebo / MuJoCoRight-handedZ-up
UnityLeft-handedY-up
Unreal EngineLeft-handedZ-up
glTF, most graphics/OpenGL-derived toolsRight-handedY-up

A rotation ported correctly component-for-component between a right-handed and a left-handed system still comes out mirrored — handedness has to be converted explicitly (typically by negating one axis and one rotation direction), not just relabeled. This is a separate problem from quaternion component order and Euler intrinsic/extrinsic, and all three compound independently when moving a rotation between, say, a CAD tool and a game engine.

Axis-angle and rotation matrices

Axis-angle (an axis vector + an angle) and 3×3 rotation matrices don't have a component-order ambiguity the way quaternions and Euler angles do — but they still inherit whatever handedness convention the surrounding system uses, and a rotation matrix's row-major vs column-major storage is a separate (and separately common) source of transposed-looking bugs when passing raw arrays between libraries.

When two numbers-that-should-match don't, checking visually beats re-deriving the math by hand — paste both representations into the rotation converter and compare the 3D gizmo.