html5 - 获取相对坐标中的设备方向旋转

html5 - Get device orientation rotation in relative coordinate

我正在尝试获取沿左右轴和上下轴的两个 deviceorientation 事件之间的方向变化,这些轴通常定义为 phone xy 轴 (https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Orientation_and_motion_data_explained)

即在 t1t2 之间,那些 phone 轴从 (x1, y1) 移动到 (x2, y2),它想要得到 (angle(x2-x1), angle(y1-y2)).

当设备处于纵向模式(与横向模式相反)时,这些轴似乎对应于 betagamma。但是当 phone 是垂直的(底部朝向地面)时, gamma 值变得非常不稳定,并从 90 度跳到 -90 度(同时,alpha 跳了 180 度)你很容易看出 here on your phone

我想避免这种情况,并且还希望获得 360 度范围内的值。这是我目前所拥有的:

// assuming portrait mode
var beta0, gamma0;
window.addEventListener('deviceorientation', function(orientation) {
  if (typeof beta0 === 'undefined') {
    beta0 = beta;
    gamma0 = gamma;
  } 

  console.log('user has moved to the left by', gamma - gamma0, ' and to the top by', beta - beta0);
});

当设备大部分处于水平状态时可以正常工作,而当设备处于垂直状态时则完全不行

好的。先简单解释下设备方向输入:

绝对坐标系,(X, Y, Z) X 是东,Y 是北,Z 是上。设备相对坐标系,(x, y, z)是这样的,x在右,y在上,z在上。然后方向角 (alpha, beta, gamma) 是描述三个简单旋转连续的角度,这些旋转将 (X, Y, Z) 变为 (x, y, z),如下所示:

  • 围绕 Z 旋转 alpha 度,从而将 (X, Y, Z) 转换为 (X', Y', Z') Z' = Z
  • 围绕 X' 旋转 beta 度,将 (X', Y', Z') 转换为 (X'', Y'', Z'') X'' = X'
  • 围绕 Y'' 旋转 gamma 度,将 (X'', Y'', Z'') 转换为 (x, y, z),其中 y = Y''

(它们被称为 Z-X'-Y'' 类型的固有 Tait-Bryan 角)

现在我们可以通过组合简单的旋转矩阵来得到相应的旋转矩阵,每个旋转矩阵分别对应三个旋转中的一个。

                                 [   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  ]

其中 A, B, Calpha, beta, gamma 的缩写,s, csin, cos 的缩写。

现在,我们感兴趣的是两个位置 (x, y, z)(x', y', z') 对应方向 (A, B, C)(A', B', C')

(x, y, z)(x', y', z') 的坐标由 R(A', B', C') * R(A, B, C)^-1 = R(A', B', C') * R(A, B, C)^T 给出,因为逆矩阵是正交(旋转)矩阵的转置。最后,如果 z' = p*x + q*y + r*z,这些旋转的角度是围绕左右轴的 p 和围绕自上而下的轴的 q(这对于小角度是正确的,它假设频繁的方向更新,否则 asin(p)asin(r) 更接近事实)

所以这里有一些javascript来获得旋转矩阵:

/*
 * 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
};

现在我们得到 angular 增量:

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;
}

最后,为了考虑屏幕方向,我们必须替换

  _z = alpha;
  _x = beta;
  _y = gamma;

来自

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;
}

请注意,累积的左右角度和上下角度将取决于用户选择的路径,不能直接从设备方向推断,但必须通过运动进行跟踪。您可以通过不同的动作到达相同的位置:

  • 方法一:

    • 保持 phone 水平并顺时针旋转 90 度。 (这既不是左右旋转也不是上下旋转)
    • 让你的 phone 保持横向模式并向你旋转 90 度。 (这既不是90度左右旋转)
    • 让你的 phone 面向你并旋转 90 度使其向上。 (这既不是90度左右旋转)
  • 方法二:

    • 将 phone 旋转 90 度,使其面向您并垂直(这是 90 度上下旋转)