I\'m trying to get the change in orientation between two deviceorientation
events along the left-right axis, and top-bottom axis, those axis being usually defin
All right. First, a simple explanation of the device orientation input:
The absolute coordinate system, (X, Y, Z)
is such that X
is East, Y
is North and Z
is up. The device relative coordinate system, (x, y, z)
is such that x
is right, y
is top and z
is up. Then the orientation angles, (alpha, beta, gamma)
are the angles that describe the succession of three simple rotations that change (X, Y, Z)
to (x, y, z)
as so:
Z
by alpha
degrees, which transforms (X, Y, Z)
to (X', Y', Z')
with Z'
= Z
X'
by beta
degrees, which transforms (X', Y', Z')
to (X'', Y'', Z'')
with X''
= X'
Y''
by gamma
degrees, which transforms (X'', Y'', Z'')
to (x, y, z)
with y
= Y''
(they are called intrinsic Tait-Bryan angles of type Z-X'-Y''
)
Now we can get the corresponding rotation matrix by composing simple rotation matrix that each correspond to one of the three rotations.
[ cC 0 sC ] [ 1 0 0 ] [ cA -sA 0 ]
R(A, B, C) = Ry(C)*Rx(B)*Rz(A) = | 0 1 0 |*| 0 cB -sB |*[ sA cA 0 ]
[ -sC 0 cC ] [ 0 sB cB ] [ 0 0 1 ]
where A, B, C
are short for alpha, beta, gamma
and s, c
for sin, cos
.
Now, we are interested in the angles of the right-left (y
axis) and top-down (x
axis) rotations deltas between two positions (x, y, z)
and (x', y', z')
that correspond to the orientations (A, B, C)
and (A', B', C')
The coordinates of (x', y', z')
in term of (x, y, z)
are given by R(A', B', C') * R(A, B, C)^-1 = R(A', B', C') * R(A, B, C)^T
since the inverse is the transpose for orthogonal (rotation) matrix. Finally, if z' = p*x + q*y + r*z
, the angle of those rotations are p
around the right-left axis and q
around the top-down one (this is true for small angles, which assume frequent orientation update, else asin(p)
and asin(r)
are closer from the truth)
So here is some javascript to get the rotation matrix:
/*
* gl-matrix is a nice library that handles rotation stuff efficiently
* The 3x3 matrix is a 9 element array
* such that indexes 0-2 correspond to the first column, 3-5 to the second column and 6-8 to the third
*/
import {mat3} from 'gl-matrix';
let _x, _y, _z;
let cX, cY, cZ, sX, sY, sZ;
/*
* return the rotation matrix corresponding to the orientation angles
*/
const fromOrientation = function(out, alpha, beta, gamma) {
_z = alpha;
_x = beta;
_y = gamma;
cX = Math.cos( _x );
cY = Math.cos( _y );
cZ = Math.cos( _z );
sX = Math.sin( _x );
sY = Math.sin( _y );
sZ = Math.sin( _z );
out[0] = cZ * cY + sZ * sX * sY, // row 1, col 1
out[1] = cX * sZ, // row 2, col 1
out[2] = - cZ * sY + sZ * sX * cY , // row 3, col 1
out[3] = - cY * sZ + cZ * sX * sY, // row 1, col 2
out[4] = cZ * cX, // row 2, col 2
out[5] = sZ * sY + cZ * cY * sX, // row 3, col 2
out[6] = cX * sY, // row 1, col 3
out[7] = - sX, // row 2, col 3
out[8] = cX * cY // row 3, col 3
};
and now we get the angular deltas:
const deg2rad = Math.PI / 180; // Degree-to-Radian conversion
let currentRotMat, previousRotMat, inverseMat, relativeRotationDelta,
totalRightAngularMovement=0, totalTopAngularMovement=0;
window.addEventListener('deviceorientation', ({alpha, beta, gamma}) => {
// init values if necessary
if (!previousRotMat) {
previousRotMat = mat3.create();
currentRotMat = mat3.create();
relativeRotationDelta = mat3.create();
fromOrientation(currentRotMat, alpha * deg2rad, beta * deg2rad, gamma * deg2rad);
}
// save last orientation
mat3.copy(previousRotMat, currentRotMat);
// get rotation in the previous orientation coordinate
fromOrientation(currentRotMat, alpha * deg2rad, beta * deg2rad, gamma * deg2rad);
mat3.transpose(inverseMat, previousRotMat); // for rotation matrix, inverse is transpose
mat3.multiply(relativeRotationDelta, currentRotMat, inverseMat);
// add the angular deltas to the cummulative rotation
totalRightAngularMovement += Math.asin(relativeRotationDelta[6]) / deg2rad;
totalTopAngularMovement += Math.asin(relativeRotationDelta[7]) / deg2rad;
}
Finally, to account for screen orientation, we have to replace
_z = alpha;
_x = beta;
_y = gamma;
by
const screen = window.screen;
const getScreenOrientation = () => {
const oriented = screen && (screen.orientation || screen.mozOrientation);
if (oriented) switch (oriented.type || oriented) {
case 'landscape-primary':
return 90;
case 'landscape-secondary':
return -90;
case 'portrait-secondary':
return 180;
case 'portrait-primary':
return 0;
}
return window.orientation|0; // defaults to zero if orientation is unsupported
};
const screenOrientation = getScreenOrientation();
_z = alpha;
if (screenOrientation === 90) {
_x = - gamma;
_y = beta;
}
else if (screenOrientation === -90) {
_x = gamma;
_y = - beta;
}
else if (screenOrientation === 180) {
_x = - beta;
_y = - gamma;
}
else if (screenOrientation === 0) {
_x = beta;
_y = gamma;
}
Note that the cumulative right-left and top-bottom angles will depend of the path chosen by the user, and cannot be infer directly from the device orientation but have to be tracked through the movement. You can arrive to the same position with different movements:
method 1:
method 2: