November 6, 2019 update:
  • There's a new open source Cardboard SDK for iOS and Android NDK that offers a streamlined API, improved device compatibility, and built-in viewer profile QR code scanning. A corresponding Unity package (SDK) is planned for a future release. We recommend that all developers actively building for Google Cardboard migrate (iOS, Android NDK) to the new Cardboard SDK.
October 15, 2019 update:
  • The Daydream View VR headset is no longer available for purchase. However, you can continue to use the existing Google VR SDK to update and distribute your apps to the Google Play Store, and make them available to users in the Daydream app.

Google VR NDK Controller

Using the Controller API requires no additional build system setup. The functionality is present in common.aar and libgvr.so, which are already being linked with your app.

Using the ControllerApi

The main entrypoint for the controller API is the ControllerApi class.

Create one at the start of your app:

// Get your gvr_context pointer from the Java GvrLayout
// (call gvrLayout.getGvrApi().getNativeGvrContext()).
jlong j_native_gvr_context = .....;

// Reinterpret it to a gvr_context*. This pointer is owned by
// the GvrLayout. Do not destroy it.
gvr_context* context = reinterpret_cast<gvr_context*>(j_native_gvr_context);

// Initialize default options for the controller API.
int32_t options = gvr::ControllerApi::DefaultOptions();

// If you need accelerometer and gyro, enable them (they are not on
// by default). Touch, buttons and orientation are on by default.
options |= GVR_CONTROLLER_ENABLE_GYRO;
options |= GVR_CONTROLLER_ENABLE_ACCEL;

// Create the ControllerApi and ControllerState objects.
std::unique_ptr<gvr::ControllerApi> controller_api(new ControllerApi);

// Initialize it. Notice that we pass the gvr_context pointer.
if (!controller_api->Init(options, gvr_context)) {
  // Handle failure. Do not proceed in case of failure (calling other
  // controller_api methods without a successful Init will crash with
  // an assert failure.
  return;
}

// Create a persistent controller state object. You must call Update() on
// this each frame to get the current controller state.
gvr::ControllerState controller_state;

// If your app is in the resumed state (the Activity got onResume()), resume
// the controller API now (if not, wait until you get onResume()).
controller_api->Resume();

Pausing and Resuming

You should pause and resume the Controller Client as well as GvrApi's head tracking when your app gets paused and resumed. When your app gets paused, call the corresponding pause methods:

gvr_api->PauseTracking();
controller_api->Pause();

When your app resumes, call the corresponding resume methods:

gvr_api->ResumeTracking();
controller_api->Resume();

Get the Controller State

When rendering a frame in your app, you can get the controller's current state via a simple polling interface that lets you access the current state:

void OnDrawFrame() {
  HandleController();
  DoFunPhysics();
  RenderAwesomeGraphics();
}

void HandleController() {
  // Read controller state, equivalent to gvr_controller_state_update().
  controller_state.Update(*controller_api);

  // To access the controller's state:
  // state.GetApiStatus() - status of the controller API
  // state.GetConnectionState() - connection state of the controller
  // state.GetOrientation() - the current orientation of the controller
  //    This is a quaternion in Start Space
  //    (X points to the right, Y points up, Z points back).
  // state.GetGyro() - current gyroscope reading
  // state.GetAccel() - current accelerometer reading
  // state.IsTouching() - whether the user is touching the touchpad
  // state.GetTouchPos() - current touch position
  //
  // Get button states:
  // state.GetButtonState(gvr::kControllerButtonApp)
  // state.GetButtonState(gvr::kControllerButtonClick)
  //
  // Transient events:
  // state.GetTouchDown() - user just started touching the touchpad
  // state.GetTouchUp() - user just stopped touching the touchpad
  //
  // Check if the app button was just released:
  // state.GetButtonUp(gvr::kControllerButtonApp)
  //
  // Check if the click button was just released:
  // state.GetButtonUp(gvr::kControllerButtonClick)
}

The transient event flags (GetTouchDown(), GetTouchUp(), GetButtonDown(), GetButtonUp(), GetRecentered(), etc) are called transient because they will be true for only one call of Update() after the event happens, then will revert back to false the next time you call Update(). Thus, when they are true, they mean that the corresponding event happened since you last called Update().

Start Space and Controller Space

The coordinate space for orientation, gyro and accelerometer are now standardized and is now consistent across the headset and the controller. The headset poses and the orientation of the controller are reported in what is called Start Space.

The controller's orientation is represented as a quaternion. The quaternion represents the controller's pose in Start Space. In other words, it specifies a rotation from Controller Space to Start Space. Start Space is a cartesian coordinate system with the following characteristics:

Start Space

  • Start Space is right-handed, so when a viewer is positioned on the positive side of the Z axis and looking at the origin, they will see that the Y axis is 90 degrees counter-clockwise from the X axis (illustrated below).

  • The Y axis points upwards (against gravity).

  • The X and Z axes lie on the ground plane.

  • Relative to the controller's pose when tracking starts, the X axis points to the right, and the Z axis points back.

  • The axes do NOT move with the controller. They remain fixed.

Some examples of orientations are shown below:

Pose1 Resulting rotation Expected quaternion2
XYZW
Initial pose, lying flat on table. None (identity) 0 0 0 1
Flat on table, rotated 90 deg to the left 90 degrees about Y axis 0 0.7071 0 0.7071
Flat on table, rotated 90 deg to the right -90 degrees about Y axis 0 -0.7071 0 0.7071
Flat on table, rotated 180 degrees (opposite to initial pose) 180 degrees about Y axis 0 1 0 0
Pointing straight up 90 degrees about X axis 0.7071 0 0 0.7071
Pointing straight down -90 degrees about X axis -0.7071 0 0 0.7071
Rolled 90 degrees to the left 90 degrees about Z axis 0 0 0.7071 0.7071
Rolled 90 degrees to the right -90 degrees about Z axis 0 0 -0.7071 0.7071

(1) All poses are given relative to the initial pose; they are not cumulative operations.

(2) [X, Y, Z] is the vector component, W is the scalar component. Note that (X, Y, Z, W) expresses the same rotation as (-X, -Y, -Z, -W).

The accelerometer and gyroscope are reported in Controller Space, which is a coordinate system that moves with the controller. In other words, the axis are defined relative to the body of the controller and they move as the controller moves.

Controller Space

  • Controller Space has the axes arranged in the same way as Start Space, but the coordinate system moves with the controller.

  • The X axis always points to the right of the controller.

  • The Y axis always points perpendicularly from the top surface of the controller.

  • The Z axis always points along the controller's body, opposite to the controller's "forward" direction.

Here are some example of accelerometer readings:

Pose* Expected accelerometer reading in m/s^2
XYZ
Lying flat on table, any heading. 0 9.8 0
Flipped on its back, on table. 0 -9.8 0
Pointing straight up 0 0 -9.8
Pointing straight down 0 0 9.8
Rolled 90 degrees to the left 9.8 0 0
Rolled 90 degrees to the right -9.8 0 0

The gyroscope expresses a rotation in radians/second about each axis. The rotation's sign follows the right-hand rule: a positive number means it's rotating clockwise when the viewer sights along that axis. Here are some example gyroscope readings:

Motion Expected gyroscope reading in radians/sec
XYZ
No motion 0 0 0
Flat on table, turning clockwise 0 negative 0
Flat on table, turning counter-clockwise 0 positive 0
Increasing pitch (moving nose up) positive 0 0
Decreasing pitch (moving nose down) negative 0 0
Rolling left 0 0 positive
Rolling right 0 0 negative

Recentering

The recentering gesture is automatically handled by the system. To recenter:

  1. Press the HOME button (don't release it yet).

  2. Point straight ahead, with the controller level with the horizon.

  3. Continue to hold HOME for at least 1 second, then release it.

When the user recenters the controller, the API will start reporting head poses and controller poses relative to the new center. You will know that a recenter has happened because the state.GetRecentered() value will be true for one frame.

Recentering only affects the controller and headset's heading (yaw) not the pitch or roll. The pitch and roll are always defined by gravity.

Monitor for Errors

On every frame, you should check state.GetApiStatus() and state.GetConnectionState(). They tell you about the health of the connection to the controller.

If gvr::ControllerApiStatus is anything other than gvr::kControllerApiOk, something is permanently wrong with the client and requires user action to fix.

The gvr::ControllerConnectionState, on the other hand, is less serious: it just tells you what the connection state of the controller is:

  • kControllerDisconnected: Controller is disconnected
  • kControllerScanning: Scanning for controller
  • kControllerConnecting: Connecting to controller
  • kControllerConnected: Controller is connected

Reserved Buttons

Even though the API currently reports them, the following buttons are reserved for system use and should not have any functionality in applications:

  • HOME - reserved for launching system UIs and recentering.

  • VOLUME UP/DOWN - fixed functionality, implemented by system.

The following buttons can be used freely by the application and have no planned system functionality:

  • APP button.

  • CLICK button (touchpad click).