html5 - Get device orientation rotation in relative coordinate

自作多情 提交于 2019-12-01 04:48:08
Guig

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:

  • rotate around Z by alpha degrees, which transforms (X, Y, Z) to (X', Y', Z') with Z' = Z
  • rotate around X' by beta degrees, which transforms (X', Y', Z') to (X'', Y'', Z'') with X'' = X'
  • rotate around 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:

    • keep your phone horizontal and rotate by 90 degrees clockwise. (this is neither a left-right nor a top-bottom rotation)
    • keep your phone in landscape mode and rotate by 90 toward you. (this is neither a 90 degrees left-right rotation)
    • keep your phone facing you and rotate by 90 so that it's up. (this is neither a 90 degrees left-right rotation)
  • method 2:

    • rotate the phone by 90 degrees so that it faces you and is vertical (this is a 90 degrees top-bottom rotation)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!