Coordinate Frame and Rotation Conventions#
This page documents the coordinate frame, quaternion, and rotation conventions used throughout the SONIC codebase. Getting these wrong causes silent bugs — the robot will move but in the wrong direction or with wrong orientation.
Coordinate Frames#
Isaac Lab / MuJoCo (simulation)#
Z-up: Gravity is along -Z. Ground plane is XY.
Right-handed: X forward, Y left, Z up.
This is the convention used during training and evaluation.
SMPL / BVH (human motion data)#
Y-up: Gravity is along -Y. Ground plane is XZ.
When loading SMPL or BVH data, set
smpl_y_up: truein the motion library config. The motion library automatically converts Y-up to Z-up internally.
Summary#
System |
Up axis |
Convention |
|---|---|---|
Isaac Lab |
Z |
Z-up, right-handed |
MuJoCo |
Z |
Z-up, right-handed |
SMPL body model |
Y |
Y-up |
BVH motion files |
Y |
Y-up |
Retargeted PKL data |
Z |
Z-up (already converted) |
Quaternion Convention#
Scalar-first (wxyz) — default throughout SONIC#
The SONIC codebase uses scalar-first (wxyz) quaternions everywhere:
q = [w, x, y, z]
This applies to:
gear_sonic/trl/utils/torch_transform.py— all rotation utilitiesgear_sonic/isaac_utils/rotations.py— Isaac Lab rotation helpers (usew_last=False)Isaac Lab APIs (
body_quat_w,root_quat_w, etc.)Motion library internal storage
Retargeted PKL data (
root_rotfield)
Scalar-last (xyzw) — scipy only#
SciPy’s Rotation class uses scalar-last (xyzw) convention:
q = [x, y, z, w]
This is only used in the data processing scripts (data_process/) when
calling scipy.spatial.transform.Rotation. The scripts convert to wxyz
before saving:
# In data processing (scipy xyzw → wxyz for storage)
root_quat_xyzw = Rotation.from_euler("xyz", euler_angles).as_quat() # scipy: xyzw
root_quat_wxyz = root_quat_xyzw[:, [3, 0, 1, 2]] # convert to wxyz
The w_last parameter#
Functions in gear_sonic/isaac_utils/rotations.py accept a w_last boolean:
quat_rotate(q, v, w_last=False) # q is wxyz (scalar-first) — this is the default
quat_rotate(q, v, w_last=True) # q is xyzw (scalar-last)
Always use w_last=False unless you’re interfacing with scipy or a system
that explicitly uses xyzw.
Quick reference#
System |
Convention |
Order |
Identity |
|---|---|---|---|
SONIC (torch_transform.py) |
wxyz |
|
|
Isaac Lab |
wxyz |
|
|
SciPy |
xyzw |
|
|
MuJoCo |
wxyz |
|
|
ROS |
xyzw |
|
|
Converting between conventions#
# wxyz → xyzw
q_xyzw = q_wxyz[..., [1, 2, 3, 0]]
# xyzw → wxyz
q_wxyz = q_xyzw[..., [3, 0, 1, 2]]
Rotation Representations#
The codebase uses multiple rotation representations depending on context:
Representation |
Shape |
Used in |
|---|---|---|
Quaternion (wxyz) |
|
Simulation, motion library, observations |
Axis-angle |
|
|
Rotation matrix |
|
Forward kinematics, 6D rotation encoding |
6D rotation |
|
Some observation terms (first 2 columns of rotation matrix) |
Euler angles |
|
CSV motion data input (converted immediately) |
Axis-angle in motion data#
The pose_aa field in retargeted PKL files stores per-body local rotations
as axis-angle vectors. The direction is the rotation axis, the magnitude is
the angle in radians:
pose_aa # (T, num_bodies, 3) — axis-angle per body, MuJoCo body order
Joint Ordering#
Isaac Lab and MuJoCo traverse the kinematic tree in different orders. The codebase provides bidirectional index mappings per robot:
from gear_sonic.envs.manager_env.robots.g1 import (
G1_ISAACLAB_TO_MUJOCO_DOF, # Reorder DOFs: IsaacLab → MuJoCo
G1_MUJOCO_TO_ISAACLAB_DOF, # Reorder DOFs: MuJoCo → IsaacLab
)
# Convert joint positions from IsaacLab order to MuJoCo order:
mujoco_joints = isaaclab_joints[..., G1_ISAACLAB_TO_MUJOCO_DOF]
Motion PKL data (dof, pose_aa) is stored in MuJoCo order. Isaac Lab
simulation uses IsaacLab order. The training pipeline handles the conversion
automatically via order_converter.py.
See Training on New Embodiments for how to define these mappings for a new robot.