Coordinate System
OpenNeuro uses a Y-up coordinate system throughout the public API:
+Y (up)
|
|
+------ +X (figure's right)
/
/
-Z (toward viewer)- +X — figure’s right
- +Y — up
- +Z — forward (into the screen, away from the viewer)
The figure starts facing +Z (into the screen). The 2D pose renderer shows a rear view (camera behind the figure, also looking along +Z).
Heading
Heading is measured in degrees clockwise from +Z:
- 0° = facing +Z (initial forward)
- 90° = facing +X (turned right)
- -90° = facing -X (turned left)
- 180° = facing -Z
GoalFrame
Goals use Y-up coordinates:
GoalFrame.new(x=5.0, y=0.0, z=10.0) # 5m right, ground level, 10m forward
GoalFrame.new(heading=90.0) # face rightThe LLM sends goals via MovementTool as (x, z) ground coordinates. MovementTool sets y=0 (ground level).
DartControl (Internal)
DartControl uses DART/SMPL’s Z-up coordinate system internally. The conversion happens at DartControl’s boundary:
Inbound (GoalFrame → DART): Y-up (x, y, z) → Z-up (-x, -z, y)
- X negated because DART +X = figure’s left
Outbound (DART → BodyPoseFrame): Z-up → Y-up
pos_x = -DART_x(negate so +X = figure’s right)pos_y = DART_z(height)pos_z = -DART_y
No other component knows about Z-up.
Z-up to Y-up Quaternion
The conversion uses a -90° rotation around X as a quaternion:
_Q_ZUP_TO_YUP = (sqrt(2)/2, -sqrt(2)/2, 0, 0) # (w, x, y, z)Applied to each bone: q_yup = Q_ZUP_TO_YUP * q_zup
Forward Kinematics
DartControl’s _features_to_body_pose() converts SMPL’s 276-dim features to BonePoses:
- Extract 6D rotations (22 joints) → convert to 3x3 matrices via Gram-Schmidt
- Walk the kinematic chain:
world_rot[j] = world_rot[parent] @ local_rot[j] - Map 22 SMPL joints → 13 OpenVR body parts
- Convert each joint: negate X, swap Y/Z, apply quaternion conversion
OpenVR
SteamVR/OpenVR also uses Y-up but with -Z forward (right-handed). The OpenVRMovement sink passes poses directly — no conversion needed since our BodyPoseFrame is already Y-up. The Z-sign difference is handled by SteamVR’s internal rendering.
Position Reporting
AgentState reports the figure’s position to the LLM as:
[Position (y-up): x=3.14, y=0.92, z=-1.50]
[Heading (from +Z clockwise): 135°]This matches the coordinate system used by GoalFrame, so the LLM can reason about position and issue goal coordinates directly.
Heading Computation
Heading is extracted from the waist quaternion:
fwd_x = 2 * (qx * qz + w * qy)
fwd_z = 1 - 2 * (qx * qx + qy * qy)
heading = -degrees(atan2(fwd_x, fwd_z))This rotates the +Z vector by the quaternion, projects to the XZ ground plane, computes the angle, and negates for clockwise convention.