Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Device orientation using Quaternion

I've written a JS SDK that listens to mobile device rotation, providing 3 inputs:

α : An angle can range between 0 and 360 degrees
β : An Angle between -180 and 180 degrees
γ : An Angle between -90 to 90 degrees

Documentation for device rotation

I have tried using Euler Angles to determine the device orientation but encountered the gimbal lock effect, that made calculation explode when the device was pointing up. That lead me to use Quaternion, that does not suffer from the gimbal lock effect.

I've found this js library that converts α,β and γ to a Quaternion, so for the following values:

α : 81.7324
β : 74.8036
γ : -84.3221

I get this Quaternion for ZXY order:

w: 0.7120695154301472
x: 0.6893688637611577
y: -0.10864439143062626
z: 0.07696733776346154

Code:

var rad = Math.PI / 180;
window.addEventListener("deviceorientation", function(ev) {

  // Update the rotation object
  var q = Quaternion.fromEuler(ev.alpha * rad, ev.beta * rad, ev.gamma * rad, 'ZXY');

  // Set the CSS style to the element you want to rotate
  elm.style.transform = "matrix3d(" + q.conjugate().toMatrix4() + ")";

}, true);

Visualizing the device orientation using 4d CSS matrix derived from the Quaternion Reflected the right device orientation (DEMO, use mobile):
enter image description here


Wrong visualizing with Euler Angels and the developer tools (DEMO, use mobile):
enter image description here


I would like to write a method that gets α,β and γ and outputs if the device is in one of the following orientations:

  • portrait
  • portrait upside down
  • landscape left
  • landscape right
  • display up
  • display down

Defining each orientation as a range of +- 45° around the relevant axes.

What approach should I take?

like image 295
Shlomi Schwartz Avatar asked Jun 26 '19 09:06

Shlomi Schwartz


People also ask

How do I get orientation from quaternion?

The simplest statement is that if a quaternion goes to the same orientation twice without going through its inverse (eg it goes from W=1 to W=0, then back to W=1 without reaching W=-1), then it has retraced its path to some degree, for example it rotates 180 clockwise around the X axis, and then rotation 180 counter ...

What is quaternion IMU?

A quaternion can be thought of as a four element vector. This vector is composed of two distinct components: a scalar and a 3 element unit vector. The scalar value, w, corresponds to an angle of rotation. The vector term, [x y z], corresponds to an axis of rotation, about which the angle or rotation is performed.

What can quaternions be used for?

Rotation and orientation quaternions have applications in computer graphics, computer vision, robotics, navigation, molecular dynamics, flight dynamics, orbital mechanics of satellites, and crystallographic texture analysis.

What is XYZW quaternion?

A quaternion is a set of 4 numbers, [x y z w], which represents rotations the following way: // RotationAngle is in radians x = RotationAxis. x * sin(RotationAngle / 2) y = RotationAxis. y * sin(RotationAngle / 2) z = RotationAxis. z * sin(RotationAngle / 2) w = cos(RotationAngle / 2)


1 Answers

Given that you've already managed to convert the Euler angles into a unit quaternion, here's a simple way to determine the orientation of the device:

  1. Take a world-space vector pointing straight up (i.e. along the +z axis) and use the quaternion (or its conjugate) to rotate it into device coordinates. (Note that you could also do this using the Euler angles directly, or using a rotation matrix, or using any other representation of the device rotation that you can apply to transform a vector.)

  2. Take the transformed vector, and find the component with the largest absolute value. This will tell you which axis of your device is pointing closest to vertical, and the sign of the component value tells you whether it's pointing up or down.

In particular:

  • if the device x axis is the most vertical, the device is in a landscape orientation;
  • if the device y axis is the most vertical, the device is in a portrait orientation;
  • if the device z axis is the most vertical, the device has the screen pointing up or down.

Here's a simple JS demo that should work at least on Chrome — or it would, except that the device orientation API doesn't seem to work in Stack Snippets at all. :( For a live demo, try this CodePen instead.

const orientations = [
  ['landscape left', 'landscape right'],  // device x axis points up/down
  ['portrait', 'portrait upside down'],   // device y axis points up/down
  ['display up', 'display down'],         // device z axis points up/down
];

const rad = Math.PI / 180;

function onOrientationChange (ev) {
  const q = Quaternion.fromEuler(ev.alpha * rad, ev.beta * rad, ev.gamma * rad, 'ZXY');

  // transform an upward-pointing vector to device coordinates
  const vec = q.conjugate().rotateVector([0, 0, 1]);

  // find the axis with the largest absolute value
  const [value, axis] = vec.reduce((acc, cur, idx) => (Math.abs(cur) < Math.abs(acc[0]) ? acc : [cur, idx]), [0, 0]);

  const orientation = orientations[axis][1 * (value < 0)];

  document.querySelector('#angles').textContent = `alpha = ${ev.alpha.toFixed(1)}°, beta = ${ev.beta.toFixed(1)}°, gamma = ${ev.gamma.toFixed(1)}°`;
  document.querySelector('#vec').textContent = `vec = ${vec.map(a => a.toFixed(3))}, dominant axis = ${axis}, value = ${value.toFixed(3)}`;
  document.querySelector('#orientation').textContent = `orientation = ${orientation}`;
}

onOrientationChange({ alpha: 0, beta: 0, gamma: 0 });
window.addEventListener("deviceorientation", onOrientationChange, true);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/quaternion.min.js"></script>
<div id="angles"></div>
<div id="vec"></div>
<div id="orientation"></div>

Note that there's apparently some inconsistency between browsers in the signs and ranges of the Euler angles supplied by the device orientation API, potentially causing the wrong signs to be computed on other browsers. You might need to do some browser sniffing to fix this, or use a wrapper library like gyronorm.js.

like image 101
Ilmari Karonen Avatar answered Sep 23 '22 22:09

Ilmari Karonen