Skip to content

Coordinate system

Odyssey describes a device in two ways:

  • In 3D, the pose: where the device is in front of the screen and which way it points, in meters.
  • In 2D, the aimpoint: where on the screen it is aimed, as a fraction of the screen.

Odyssey uses a right-handed, Y-up coordinate system, in meters, with its origin at the top-left corner of the screen:

  • +X points right, along the top edge of the screen.
  • +Y points up.
  • +Z points out of the screen, toward the shooter. A device therefore looks along −Z, into the screen.
Odyssey's 3D coordinate system The screen lies in the XY plane with the origin at its top-left corner. +X points right, +Y up, and +Z out of the screen toward the shooter. A device in front of the screen looks along its -Z axis; where that line meets the screen is the aimpoint. screen (z = 0) pose.translation aimpoint line of sight (the device's −Z) device +X right +Y up +Z toward the shooter origin top-left corner of the screen

It is the same convention as Bevy, Godot, three.js, and OpenGL. Unity flips the Z axis; Unreal Engine and Blender put Z up.

To check a convention, make the sign with your hand: thumb along +X, index finger along +Y, middle finger along +Z. Odyssey’s axes fit your right hand, with the middle finger pointing at you; Unity’s fit your left hand.

Axis conventions in common engines Two hands make the axis sign: thumb +X pointing right, index finger +Y up, middle finger +Z. On the right hand, palm toward you, the middle finger points at you; on the left hand, back toward you, it points away. Below them, a two-by-two chart. Rows: Y up, Z up. Columns: right-handed, left-handed. Odyssey is right-handed with Y up and forward along -Z, like Bevy, Godot, three.js, and OpenGL. Unity is left-handed with Y up and forward along +Z. Blender is right-handed with Z up and forward along -Y. Unreal Engine is left-handed with Z up and forward along +X. Right-handed Z points toward you X Y Z Left-handed Z points away from you X Y Z Y up Z up forward: −Z Odyssey Bevy Godot three.js OpenGL forward: −Y Blender forward: +Z Unity forward: +X Unreal Engine Same thumb and index finger, mirrored hand: only the middle finger, Z, changes direction.
Logos are trademarks of their owners and identify each engine. Godot logo by Andrea Calabró, CC BY 4.0.

What this means in practice:

  • A shooter in front of the screen has a positive translation.z: their distance from the plane of the screen.
  • Anywhere below the top edge has a negative translation.y. The middle of a 1.6 m × 0.9 m screen is at (0.8, -0.45, 0).
  • Up is the screen’s up. On a tilted screen, Y tilts with it.
  • Each screen has its own origin. When screen_id changes, positions are measured from the new screen’s top-left corner.

pose.translation (x, y, z) is the position of the device’s tracking camera, in meters. Zeroing never changes it.

pose.rotation is a 3×3 rotation matrix with entries m11 to m33 (row, column). It turns directions on the device into directions in the screen’s frame, and its columns are the device’s own axes:

The device's own axes A pistol with the Odyssey tracker under its barrel. Its own axes start at the tracker's camera: +X to the device's right, +Y up out of its top, and +Z back toward the shooter. Its line of sight, the way the tracker looks, is -Z. The columns of pose.rotation are these axes in the screen's frame. −Z +X +Y +Z origin: the tracker's camera pose.rotation +X the device's right column 1 m11, m21, m31 +Y up, out of its top column 2 m12, m22, m32 +Z back, toward the shooter column 3 m13, m23, m33 −Z line of sight: where the tracker looks

The device’s line of sight, the way its tracker looks, is its −Z axis:

line of sight = (-m13, -m23, -m33)

A device held level and aimed straight at the screen has the identity rotation: its axes line up with the screen’s.

Like UI and image coordinates in most engines, the aimpoint has its origin at the top-left and its y axis pointing down. It is a fraction of the screen, so it does not depend on resolution.

Aimpoint coordinates The aimpoint is a fraction of the screen. (0, 0) is the top-left corner, (1, 1) the bottom-right corner, and (0.5, 0.5) the center. x grows to the right and y grows downward. Values outside 0 to 1 mean the device is aimed past an edge. x y (0, 0) (1, 0) (0, 1) (1, 1) (0.5, 0.5) center (1.14, 0.3) aimed past the right edge Origin at the top-left; y grows downward, like UI and image coordinates.

To get a pixel, multiply by the size of what is being shot at. Where the pixel origin is the top-left, as in Unreal Engine, Godot, Bevy, and most 2D and UI APIs:

pixel = (aimpoint.x * width, aimpoint.y * height)

Where it is the bottom-left, as in Unity’s Screen coordinates and OpenGL, flip y:

pixel = (aimpoint.x * width, (1 - aimpoint.y) * height)

The aimpoint already includes the device’s zero. On a curved or irregular display measured during calibration, it follows the measured surface.

get_screen_info_by_id returns a screen’s corners, tl, tr, bl, and br, in meters. They come from the screen calibration, which measures y downward, unlike the pose. To use a corner in the pose’s frame, negate its y:

corner in the pose's frame = (corner.x, -corner.y, 0)

For a screen calibrated with Odyssey Desktop’s automatic calibration, tl is (0, 0) and br is (width, height). Read the corners rather than assuming this.

Positions are relative to the screen’s top-left corner. Place an object at your scene’s screen corner and parent tracked objects to it.

Odyssey Unity Unreal Engine Godot, Bevy
+X, right +X +Y +X
+Y, up +Y +Z +Y
+Z, toward the shooter −Z −X +Z
meters meters centimeters, × 100 meters
using UnityEngine;
using Ohc = OdysseyArm.HubClient;
static class OdysseyToUnity
{
// Unity is left-handed: flip z.
public static Vector3 Position(Ohc.uniffi.Pose pose) =>
new Vector3(pose.Translation.X, pose.Translation.Y, -pose.Translation.Z);
// Forward (+Z) is the device's line of sight; up (+Y) is its top.
public static Quaternion Rotation(Ohc.uniffi.Pose pose)
{
var r = pose.Rotation;
return Quaternion.LookRotation(
new Vector3(-r.M13, -r.M23, r.M33),
new Vector3(r.M12, r.M22, -r.M32));
}
}

In Godot and Bevy, a camera or node looks along −Z, just like the device, so a camera given this transform looks where the device looks.

Value Measured in Axes
aimpoint fractions of the screen, from its top-left corner x right, y down
pose.translation meters, from the screen’s top-left corner x right, y up, z toward the shooter
pose.rotation its columns are the device’s axes right, up, back (line of sight is −Z)
Screen corners meters, usually from the top-left corner x right, y down
distance meters, from the device along its line of sight