File size: 4,713 Bytes
700dd75 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | # 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: true` in 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 utilities
- `gear_sonic/isaac_utils/rotations.py` β Isaac Lab rotation helpers (use `w_last=False`)
- Isaac Lab APIs (`body_quat_w`, `root_quat_w`, etc.)
- Motion library internal storage
- Retargeted PKL data (`root_rot` field)
### Scalar-last (xyzw) β scipy only
[SciPy's Rotation class](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.html)
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:
```python
# 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:
```python
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 | `[w, x, y, z]` | `[1, 0, 0, 0]` |
| Isaac Lab | wxyz | `[w, x, y, z]` | `[1, 0, 0, 0]` |
| SciPy | xyzw | `[x, y, z, w]` | `[0, 0, 0, 1]` |
| MuJoCo | wxyz | `[w, x, y, z]` | `[1, 0, 0, 0]` |
| ROS | xyzw | `[x, y, z, w]` | `[0, 0, 0, 1]` |
### Converting between conventions
```python
# 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) | `(..., 4)` | Simulation, motion library, observations |
| Axis-angle | `(..., 3)` | `pose_aa` field in motion PKLs |
| Rotation matrix | `(..., 3, 3)` | Forward kinematics, 6D rotation encoding |
| 6D rotation | `(..., 6)` | Some observation terms (first 2 columns of rotation matrix) |
| Euler angles | `(..., 3)` | 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:
```python
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:
```python
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](../user_guide/new_embodiments.md) for how to
define these mappings for a new robot.
|