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

Gui*_*uig 6 mobile html5 rotation euler-angles device-orientation

我试图改变deviceorientation沿左右轴和上下轴的两个事件之间的方向,这些轴通常被定义为电话xy轴(https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Orientation_and_motion_data_explained)

即瞬间之间t1t2其中那些电话线从移动(x1, y1)(x2, y2),它想获得(angle(x2-x1), angle(y1-y2)).

当设备处于纵向模式时(与横向模式相反),这些轴似乎对应于betagamma.然而,当手机处于垂直状态(底部朝向地面)时,该gamma值变得非常不稳定,并且从90度跳到-90度(在相同的情况下,alpha跳跃180度)您可以在手机上轻松看到

我想避免这种情况,并且还获得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);
});
Run Code Online (Sandbox Code Playgroud)

当设备大部分是水平的时候工作正常,而当它是垂直的时候根本不工作

Gui*_*uig 8

行.首先,设备方向输入的简单说明:

绝对坐标系,(X, Y, Z)X东,YZ是向上.设备相对坐标系,(x, y, z)x正确的,y顶部z是向上的.然后方位角度,(alpha, beta, gamma)是描述的是改变三个简单的旋转继承的角度(X, Y, Z)(x, y, z)为这样:

  • Zalpha度,其将(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''

(它们被称为内在的Tait-Bryan角型Z-X'-Y'')

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

                                 [   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  ]
Run Code Online (Sandbox Code Playgroud)

其中,A, B, C短的alpha, beta, gammas, csin, cos.

现在,我们感兴趣的是左右(的角度y轴)和自顶向下(x轴)旋转三角洲两个位置之间(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
};
Run Code Online (Sandbox Code Playgroud)

现在我们得到了角度增量:

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;
}
Run Code Online (Sandbox Code Playgroud)

最后,考虑到屏幕方向,我们必须更换

  _z = alpha;
  _x = beta;
  _y = gamma;
Run Code Online (Sandbox Code Playgroud)

通过

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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 方法1:

    • 保持手机水平并顺时针旋转90度.(这既不是左右旋转也不是上下旋转)
    • 将手机置于横向模式并向您旋转90度.(这不是90度左右旋转)
    • 让你的手机面向你并旋转90度,这样就可以了.(这不是90度左右旋转)
  • 方法2:

    • 将手机旋转90度,使其面向您并垂直(这是一个90度的上下旋转)