Three.js DeviceOrientationControls Gyroscope Mapping

This article explains how the DeviceOrientationControls class in Three.js maps a mobile device’s gyroscope and accelerometer data to rotate the virtual camera. It covers the retrieval of physical orientation angles, the translation of these angles into 3D quaternions, and the coordinate system alignments required to synchronize physical phone movement with virtual camera rotation.

Understanding the Input: Device Orientation API

The browser’s DeviceOrientationEvent provides real-time rotation data from the device’s gyroscope and accelerometer. This data is delivered via three Euler angles measured in degrees:

The Coordinate System Mismatch

The primary challenge in mapping this data is that the device’s coordinate system does not natively match the Three.js 3D world coordinate system.

In the device coordinate system: * The Z-axis points straight up out of the screen. * The X-axis points to the right of the screen. * The Y-axis points to the top of the screen.

In Three.js: * The Y-axis points straight up. * The X-axis points to the right. * The Z-axis points out of the screen toward the viewer.

To map these systems accurately, DeviceOrientationControls must perform a series of mathematical rotations.

Step-by-Step Mathematical Mapping

Three.js uses quaternions to represent rotations and avoid gimbal lock. The mapping from raw device angles to the final camera rotation occurs in four distinct steps:

1. Converting Angles to Radians and Euler Ordering

First, the alpha, beta, and gamma degrees are converted into radians. DeviceOrientationControls instantiates a Three.js Euler object using these angles.

To prevent gimbal lock during standard phone movement, Three.js applies these rotations in a specific order: ‘YXZ’.

const alpha = deviceOrientation.alpha ? THREE.MathUtils.degToRad(deviceOrientation.alpha) : 0;
const beta = deviceOrientation.beta ? THREE.MathUtils.degToRad(deviceOrientation.beta) : 0;
const gamma = deviceOrientation.gamma ? THREE.MathUtils.degToRad(deviceOrientation.gamma) : 0;

// Set Euler angles with YXZ order
const euler = new THREE.Euler(beta, alpha, -gamma, 'YXZ');

2. Converting Euler Angles to a Quaternion

Once the Euler angles are defined, they are converted into a temporary quaternion (deviceQuaternion). This represents the raw orientation of the device relative to the earth’s coordinate frame.

const deviceQuaternion = new THREE.Quaternion().setFromEuler(euler);

3. Adjusting for Screen Orientation

Mobile devices can be held in portrait or landscape mode, which rotates the screen’s coordinate axes relative to the physical device. To correct for this, DeviceOrientationControls detects the screen orientation angle (window.orientation) and applies a correction rotation around the camera’s Z-axis.

const screenResolutionAngle = window.orientation ? THREE.MathUtils.degToRad(window.orientation) : 0;
const screenTransform = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), -screenResolutionAngle);

4. Aligning with the Three.js World Frame

Finally, the device’s orientation must be aligned with the virtual world space of Three.js. By default, the device orientation API’s “forward” vector points north, whereas a Three.js camera’s default “forward” vector points down the negative Z-axis.

To reconcile this, a constant sensor-to-world offset quaternion is applied. This is a -90 degree (\(- \pi / 2\) radians) rotation around the physical X-axis:

const worldAlign = new THREE.Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));

Calculating the Final Camera Rotation

The camera’s final rotation is calculated by multiplying these quaternions together in the correct sequence. Because quaternion multiplication is non-commutative (order matters), they are combined as follows:

// final Rotation = worldAlign * deviceQuaternion * screenTransform
camera.quaternion.copy(worldAlign)
                 .multiply(deviceQuaternion)
                 .multiply(screenTransform);

This sequence rotates the virtual camera so that when the user rotates their physical phone to the left, the virtual camera rotates to the left; when they tilt it up, the camera tilts up, maintaining a 1:1 spatial relationship with the physical world.