Skip to content

Head Tracking API

balukin edited this page Jun 22, 2020 · 14 revisions

Table of Conents

Quick facts:

  • Works as REP ZMQ socket listening at port assigned by control service while VRidge is running.
  • Uses protobuf-serialized messages
  • If you use any of the rotational writes, VRidge will suspend using phone rotational data for few seconds (so the camera doesn't jump around between phone rotation and your rotation). If you don't send rotational data for few seconds (currently: 5), VRidge will resume using phone rotational data.

Connecting

See Control channel page to find out how to connect to this endpoint.

Head tracking calls

There are multiple packet types that you can send, they can be divided into 3 main categories. See struct definition to see byte layout. All responses return ReplyCode: 0 on success. See response packets for error codes.

Recentering

Send a REQ packet with TaskType = 50.

Data[] array is ignored. This is equivalent to using recenter hotkey.

Changing HMD state

You can notify SteamVR that HMD is outside tracking range. SteamVR currently shuts off display when HMD is outside range.

Send a REQ packet with TaskType = 254. First byte of Data[] array should be equal to:

// Currently tracked.
Active = 0

// Not in tracking range or tracking unavailable due to other reasons.
TempUnavailable = 1

Writing tracking data

Note: Starting with VRidge v2.3, when reprojection-enabled device (Oculus Go, Gear VR, Vive Focus, Daydream phones) is connected, only positional data is allowed to be written ("Sends position only" packet types). These devices already have high quality rotational sensors and the experience can only be worse when paired with detached external rotational tracking system. This doesn't block writing positional data and/or yaw offsets.

In source code you will also find pose matrix formats. They are still working but they are deprecated and removed from this table.

Code Description Data[] contents Data format VR rotation VR position Notes
3 Sends radian rotation and position float[6] [pitch(+up), yaw (+to left),roll(+left), X, Y, Z] From the array From the array Rotation in radians
4 Sends quaternion rotation and position float[7] [X, Y, Z, W, X, Y, Z] (quaternion, then position) From the array From the array
5 Sends position only float[3] [X, Y, Z] From the phone From the array

Providing asynchronous offset

You can read current phone tracking data, compare it to your own data and set rotational offset that will be applied to each mobile frame. This offset lasts until canceled or restarted. Due to nature of rotational drift it is recommended to update the offset every now and then.

REQ

Code Description Data[] contents Data format Notes
199 Requests current pose null null Returns most recent pose used to render along with offset data
201 Sets an offset to be used by future phone data float[1] yaw Lasts until shutdown or reset with call below
210 Resets async offset null null Resets offset sent with 201

REP

Code Description Response Notes
2 Sending current tracked pose TrackedPose In response to 199.

TrackedPose is formatted as below:

[ProtoContract]
public class TrackedPose
{
    /// <summary>
    /// Current head orientation as 4 element XYZW quaternion.
    /// </summary>
    [ProtoMember(1)]
    public float[] HeadOrientation;

    /// <summary>
    /// Current head position as 3 element XYZ vector.
    /// </summary>
    [ProtoMember(2)]
    public float[] HeadPosition;

    /// <summary>
    /// Current offset applied to each head-related (Controller w/ HeadRelation.IsInHeadSpace or internal VRidge mobile tracking data) pose due to user recenter.
    /// In 99% cases, you can forget about it.
    /// </summary>
    [ProtoMember(3)]
    public float RecenterYawOffset;

    /// <summary>
    /// Current offset applied to each pose due to API SetYawOffset call.
    /// </summary>
    [ProtoMember(4)]
    public float ApiYawOffset;

}

Packet layout

Important note: Data array is always 64 byte long. DataLength holds actual number of meaningful bytes that should be used.

Request packets

[ProtoContract]
public struct HeadTrackingRequest
{
    private const int CurrentVersion = 3;

    /// <summary>
    /// Should always be 3 for v3
    /// </summary>
    [ProtoMember(1)]
    public int Version;

    /// <summary>
    /// Describes how API should handle the incoming data.
    /// See Writing tracking data paragraph for task codes.
    /// </summary>        
    [ProtoMember(2)]
    public byte TaskType;

    /// <summary>
    /// Tracking data itself.
    /// </summary>        
    [ProtoMember(3)]
    public byte[] Data;
}

Response packets:

[ProtoContract]
public struct HeadTrackingResponse
{        
    [ProtoMember(1)]
    public int Version;

    [ProtoMember(2)]
    public byte ReplyCode;

    // Only filled in response to current pose requests
    [ProtoMember(4)]
    public TrackedPose TrackedPose;
}

You can find those files here.

See example implementation.